From 95d824542fa5f779eb1353860ea54ba1fa19add9 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sun, 30 Jul 2023 00:11:26 -0700 Subject: [PATCH] Added ability to delete reports. --- components/common/ConfirmDeleteForm.js | 29 +++++++++ components/common/LinkButton.js | 12 ++++ components/common/LinkButton.module.css | 28 +++++++++ components/pages/reports/ReportsTable.js | 59 ++++++++++++------- .../pages/websites/WebsiteReportsPage.js | 8 ++- hooks/useReports.js | 19 +++++- lib/auth.ts | 4 ++ pages/api/reports/[id].ts | 40 ++++++++----- 8 files changed, 159 insertions(+), 40 deletions(-) create mode 100644 components/common/ConfirmDeleteForm.js create mode 100644 components/common/LinkButton.js create mode 100644 components/common/LinkButton.module.css 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 ( +
+

+ {name} }} /> +

+ + + {formatMessage(labels.delete)} + + + +
+ ); +} + +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); };