From 6e128b2f38d8a5c2415e29fc341ba2bfbd17f10b Mon Sep 17 00:00:00 2001 From: Chris Walsh Date: Tue, 10 Aug 2021 14:03:55 -0700 Subject: [PATCH] Add reset website statistics to settings --- components/forms/ResetForm.js | 98 ++++++++++++++++++++++++++ components/settings/WebsiteSettings.js | 18 +++++ lang/en-US.json | 3 + lib/queries.js | 4 ++ pages/api/website/[id]/reset.js | 20 ++++++ 5 files changed, 143 insertions(+) create mode 100644 components/forms/ResetForm.js create mode 100644 pages/api/website/[id]/reset.js diff --git a/components/forms/ResetForm.js b/components/forms/ResetForm.js new file mode 100644 index 00000000..791039ac --- /dev/null +++ b/components/forms/ResetForm.js @@ -0,0 +1,98 @@ +import React, { useState } from 'react'; +import { FormattedMessage } from 'react-intl'; +import { Formik, Form, Field } from 'formik'; +import Button from 'components/common/Button'; +import FormLayout, { + FormButtons, + FormError, + FormMessage, + FormRow, +} from 'components/layout/FormLayout'; +import usePost from 'hooks/usePost'; + +const CONFIRMATION_WORD = 'RESET'; + +const validate = ({ confirmation }) => { + const errors = {}; + + if (confirmation !== CONFIRMATION_WORD) { + errors.confirmation = !confirmation ? ( + + ) : ( + + ); + } + + return errors; +}; + +export default function ResetForm({ values, onSave, onClose }) { + const post = usePost(); + const [message, setMessage] = useState(); + + const handleSubmit = async ({ type, id }) => { + const { ok, data } = await post(`/api/${type}/${id}/reset`); + + if (ok) { + onSave(); + } else { + setMessage( + data || , + ); + } + }; + + return ( + + + {props => ( +
+
+ {values.name} }} + /> +
+
+ +
+

+ {CONFIRMATION_WORD} }} + /> +

+ +
+ + +
+
+ + + + + {message} +
+ )} +
+
+ ); +} diff --git a/components/settings/WebsiteSettings.js b/components/settings/WebsiteSettings.js index 686605f2..ee23e549 100644 --- a/components/settings/WebsiteSettings.js +++ b/components/settings/WebsiteSettings.js @@ -7,6 +7,7 @@ import Button from 'components/common/Button'; import PageHeader from 'components/layout/PageHeader'; import Modal from 'components/common/Modal'; import WebsiteEditForm from 'components/forms/WebsiteEditForm'; +import ResetForm from 'components/forms/ResetForm'; import DeleteForm from 'components/forms/DeleteForm'; import TrackingCodeForm from 'components/forms/TrackingCodeForm'; import ShareUrlForm from 'components/forms/ShareUrlForm'; @@ -16,6 +17,7 @@ import Toast from 'components/common/Toast'; import Favicon from 'components/common/Favicon'; import Pen from 'assets/pen.svg'; import Trash from 'assets/trash.svg'; +import Reset from 'assets/redo.svg'; import Plus from 'assets/plus.svg'; import Code from 'assets/code.svg'; import LinkIcon from 'assets/link.svg'; @@ -24,6 +26,7 @@ import styles from './WebsiteSettings.module.css'; export default function WebsiteSettings() { const [editWebsite, setEditWebsite] = useState(); + const [resetWebsite, setResetWebsite] = useState(); const [deleteWebsite, setDeleteWebsite] = useState(); const [addWebsite, setAddWebsite] = useState(); const [showCode, setShowCode] = useState(); @@ -55,6 +58,9 @@ export default function WebsiteSettings() { + @@ -96,6 +102,7 @@ export default function WebsiteSettings() { function handleClose() { setAddWebsite(null); setEditWebsite(null); + setResetWebsite(null); setDeleteWebsite(null); setShowCode(null); setShowUrl(null); @@ -141,6 +148,17 @@ export default function WebsiteSettings() { )} + {resetWebsite && ( + } + > + + + )} {deleteWebsite && ( } diff --git a/lang/en-US.json b/lang/en-US.json index 4aec2554..799aa443 100644 --- a/lang/en-US.json +++ b/lang/en-US.json @@ -19,6 +19,7 @@ "label.delete": "Delete", "label.delete-account": "Delete account", "label.delete-website": "Delete website", + "label.reset-website": "Reset statistics", "label.dismiss": "Dismiss", "label.domain": "Domain", "label.edit": "Edit", @@ -58,8 +59,10 @@ "label.view-details": "View details", "label.websites": "Websites", "message.active-users": "{x} current {x, plural, one {visitor} other {visitors}}", + "message.confirm-reset": "Are your sure you want to reset {target}'s statistics?", "message.confirm-delete": "Are your sure you want to delete {target}?", "message.copied": "Copied!", + "message.reset-warning": "All statistics for this website will be deleted, but your tracking code will remain intact.", "message.delete-warning": "All associated data will be deleted as well.", "message.failure": "Something went wrong.", "message.get-share-url": "Get share URL", diff --git a/lib/queries.js b/lib/queries.js index ae39f685..4e9ce892 100644 --- a/lib/queries.js +++ b/lib/queries.js @@ -141,6 +141,10 @@ export async function updateWebsite(website_id, data) { ); } +export async function resetWebsite(website_id) { + return runQuery(prisma.$queryRaw`delete from session where website_id=${website_id}`); +} + export async function deleteWebsite(website_id) { return runQuery( /* Prisma bug, does not cascade on non-nullable foreign keys diff --git a/pages/api/website/[id]/reset.js b/pages/api/website/[id]/reset.js new file mode 100644 index 00000000..2830ccc8 --- /dev/null +++ b/pages/api/website/[id]/reset.js @@ -0,0 +1,20 @@ +import { resetWebsite } from 'lib/queries'; +import { methodNotAllowed, ok, unauthorized } from 'lib/response'; +import { allowQuery } from 'lib/auth'; + +export default async (req, res) => { + const { id } = req.query; + const websiteId = +id; + + if (req.method === 'POST') { + if (!(await allowQuery(req))) { + return unauthorized(res); + } + + await resetWebsite(websiteId); + + return ok(res); + } + + return methodNotAllowed(res); +};