diff --git a/components/common/ConfirmDeleteForm.js b/components/common/ConfirmDeleteForm.js
new file mode 100644
index 00000000..3496a305
--- /dev/null
+++ b/components/common/ConfirmDeleteForm.js
@@ -0,0 +1,29 @@
+import { useState } from 'react';
+import { Button, LoadingButton, Form, FormButtons } from 'react-basics';
+import useMessages from 'hooks/useMessages';
+
+export function ConfirmDeleteForm({ name, onConfirm, onClose }) {
+ const [loading, setLoading] = useState(false);
+ const { formatMessage, labels, messages, FormattedMessage } = useMessages();
+
+ const handleConfirm = () => {
+ setLoading(true);
+ onConfirm();
+ };
+
+ return (
+
+ );
+}
+
+export default ConfirmDeleteForm;
diff --git a/components/common/LinkButton.js b/components/common/LinkButton.js
new file mode 100644
index 00000000..8c050147
--- /dev/null
+++ b/components/common/LinkButton.js
@@ -0,0 +1,12 @@
+import Link from 'next/link';
+import { Icon, Icons, Text } from 'react-basics';
+import styles from './LinkButton.module.css';
+
+export default function LinkButton({ href, icon, children }) {
+ return (
+
+ {icon || }
+ {children}
+
+ );
+}
diff --git a/components/common/LinkButton.module.css b/components/common/LinkButton.module.css
new file mode 100644
index 00000000..ae8a3b62
--- /dev/null
+++ b/components/common/LinkButton.module.css
@@ -0,0 +1,28 @@
+.button {
+ display: flex;
+ align-items: center;
+ align-self: flex-start;
+ white-space: nowrap;
+ gap: var(--size200);
+ font-family: inherit;
+ color: var(--base900);
+ background: var(--base100);
+ border: 1px solid transparent;
+ border-radius: var(--border-radius);
+ min-height: var(--base-height);
+ padding: 0 var(--size600);
+ position: relative;
+ cursor: pointer;
+}
+
+.button:hover {
+ background: var(--base200);
+}
+
+.button:active {
+ background: var(--base300);
+}
+
+.button:visited {
+ color: var(--base900);
+}
diff --git a/components/pages/reports/ReportsTable.js b/components/pages/reports/ReportsTable.js
index bcc97204..244740e1 100644
--- a/components/pages/reports/ReportsTable.js
+++ b/components/pages/reports/ReportsTable.js
@@ -1,9 +1,12 @@
-import Link from 'next/link';
-import { Button, Text, Icon, Icons } from 'react-basics';
+import { useState } from 'react';
+import { Flexbox, Icon, Icons, Text, Button, Modal } from 'react-basics';
+import LinkButton from 'components/common/LinkButton';
import SettingsTable from 'components/common/SettingsTable';
-import useMessages from 'hooks/useMessages';
+import ConfirmDeleteForm from 'components/common/ConfirmDeleteForm';
+import { useMessages } from 'hooks';
-export function ReportsTable({ data = [] }) {
+export function ReportsTable({ data = [], onDelete = () => {} }) {
+ const [report, setReport] = useState(null);
const { formatMessage, labels } = useMessages();
const columns = [
@@ -13,23 +16,39 @@ export function ReportsTable({ data = [] }) {
{ name: 'action', label: ' ' },
];
- return (
-
- {row => {
- const { id } = row;
+ const handleConfirm = () => {
+ onDelete(report.id);
+ };
- return (
-
-
-
- );
- }}
-
+ return (
+ <>
+
+ {row => {
+ const { id } = row;
+
+ return (
+
+ {formatMessage(labels.view)}
+
+
+ );
+ }}
+
+ {report && (
+
+ setReport(null)}
+ />
+
+ )}
+ >
);
}
diff --git a/components/pages/websites/WebsiteReportsPage.js b/components/pages/websites/WebsiteReportsPage.js
index b6f41bac..56927028 100644
--- a/components/pages/websites/WebsiteReportsPage.js
+++ b/components/pages/websites/WebsiteReportsPage.js
@@ -7,7 +7,11 @@ import WebsiteHeader from './WebsiteHeader';
export function WebsiteReportsPage({ websiteId }) {
const { formatMessage, labels } = useMessages();
- const { reports, error, isLoading } = useReports(websiteId);
+ const { reports, error, isLoading, deleteReport } = useReports(websiteId);
+
+ const handleDelete = async id => {
+ await deleteReport(id);
+ };
return (
@@ -22,7 +26,7 @@ export function WebsiteReportsPage({ websiteId }) {
-
+
);
}
diff --git a/hooks/useReports.js b/hooks/useReports.js
index 90aa5cf5..f4369eec 100644
--- a/hooks/useReports.js
+++ b/hooks/useReports.js
@@ -1,10 +1,23 @@
+import { useState } from 'react';
import useApi from './useApi';
export function useReports(websiteId) {
- const { get, useQuery } = useApi();
- const { data, error, isLoading } = useQuery(['reports'], () => get(`/reports`, { websiteId }));
+ const [modified, setModified] = useState(Date.now());
+ const { get, useQuery, del, useMutation } = useApi();
+ const { mutate } = useMutation(reportId => del(`/reports/${reportId}`));
+ const { data, error, isLoading } = useQuery(['reports:website', { websiteId, modified }], () =>
+ get(`/reports`, { websiteId }),
+ );
- return { reports: data, error, isLoading };
+ const deleteReport = id => {
+ mutate(id, {
+ onSuccess: () => {
+ setModified(Date.now());
+ },
+ });
+ };
+
+ return { reports: data, error, isLoading, deleteReport };
}
export default useReports;
diff --git a/lib/auth.ts b/lib/auth.ts
index 9cd0ae96..10f7fbca 100644
--- a/lib/auth.ts
+++ b/lib/auth.ts
@@ -105,6 +105,10 @@ export async function canUpdateReport({ user }: Auth, report: Report) {
return user.id == report.userId;
}
+export async function canDeleteReport(auth: Auth, report: Report) {
+ return canUpdateReport(auth, report);
+}
+
export async function canCreateTeam({ user }: Auth) {
if (user.isAdmin) {
return true;
diff --git a/pages/api/reports/[id].ts b/pages/api/reports/[id].ts
index 20731946..85bc302c 100644
--- a/pages/api/reports/[id].ts
+++ b/pages/api/reports/[id].ts
@@ -1,9 +1,9 @@
-import { canUpdateReport, canViewReport } from 'lib/auth';
+import { canUpdateReport, canViewReport, canDeleteReport } from 'lib/auth';
import { useAuth, useCors } from 'lib/middleware';
import { NextApiRequestQueryBody } from 'lib/types';
import { NextApiResponse } from 'next';
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
-import { getReportById, updateReport } from 'queries';
+import { getReportById, updateReport, deleteReport } from 'queries';
export interface ReportRequestQuery {
id: string;
@@ -24,31 +24,29 @@ export default async (
await useCors(req, res);
await useAuth(req, res);
+ const { id: reportId } = req.query;
+ const {
+ user: { id: userId },
+ } = req.auth;
+
if (req.method === 'GET') {
- const { id: reportId } = req.query;
+ const report = await getReportById(reportId);
- const data = await getReportById(reportId);
-
- if (!(await canViewReport(req.auth, data))) {
+ if (!(await canViewReport(req.auth, report))) {
return unauthorized(res);
}
- data.parameters = JSON.parse(data.parameters);
+ report.parameters = JSON.parse(report.parameters);
- return ok(res, data);
+ return ok(res, report);
}
if (req.method === 'POST') {
- const { id: reportId } = req.query;
- const {
- user: { id: userId },
- } = req.auth;
-
const { websiteId, type, name, description, parameters } = req.body;
- const data = await getReportById(reportId);
+ const report = await getReportById(reportId);
- if (!(await canUpdateReport(req.auth, data))) {
+ if (!(await canUpdateReport(req.auth, report))) {
return unauthorized(res);
}
@@ -64,5 +62,17 @@ export default async (
return ok(res, result);
}
+ if (req.method === 'DELETE') {
+ const report = await getReportById(reportId);
+
+ if (!(await canDeleteReport(req.auth, report))) {
+ return unauthorized(res);
+ }
+
+ await deleteReport(reportId);
+
+ return ok(res);
+ }
+
return methodNotAllowed(res);
};