Skip to content

Commit 61a0123

Browse files
committed
feat: delete a book
1 parent f357f5c commit 61a0123

File tree

5 files changed

+94
-8
lines changed

5 files changed

+94
-8
lines changed

app/components/ui/sonner.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export const Sonner = ({ ...props }: ToasterProps) => {
77
<Toaster
88
theme={theme === 'dark' ? 'dark' : 'light'}
99
className="toaster group mt-safe-top"
10-
position="top-right"
10+
position="top-center"
1111
offset={{
1212
top: 'calc(16px + env(safe-area-inset-top))',
1313
bottom: 'calc(16px + env(safe-area-inset-bottom))',

app/features/book/manager/page-book.tsx

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,23 @@
11
import { getUiState } from '@bearstudio/ui-state';
22
import { ORPCError } from '@orpc/client';
3-
import { useQuery } from '@tanstack/react-query';
4-
import { Link } from '@tanstack/react-router';
3+
import { useQuery, useQueryClient } from '@tanstack/react-query';
4+
import { Link, useRouter } from '@tanstack/react-router';
5+
import { useCanGoBack } from '@tanstack/react-router';
56
import { AlertCircleIcon, PencilLineIcon, Trash2Icon } from 'lucide-react';
7+
import { toast } from 'sonner';
68

79
import { orpc } from '@/lib/orpc/client';
810

911
import { BackButton } from '@/components/back-button';
1012
import { PageError } from '@/components/page-error';
1113
import { Button } from '@/components/ui/button';
1214
import { Card, CardContent } from '@/components/ui/card';
15+
import { ConfirmResponsiveDrawer } from '@/components/ui/confirm-responsive-drawer';
1316
import { ResponsiveIconButton } from '@/components/ui/responsive-icon-button';
1417
import { Skeleton } from '@/components/ui/skeleton';
1518
import { Spinner } from '@/components/ui/spinner';
1619

20+
import { WithPermissions } from '@/features/auth/with-permission';
1721
import { BookCover } from '@/features/book/book-cover';
1822
import {
1923
PageLayout,
@@ -23,6 +27,9 @@ import {
2327
} from '@/layout/manager/page-layout';
2428

2529
export const PageBook = (props: { params: { id: string } }) => {
30+
const queryClient = useQueryClient();
31+
const router = useRouter();
32+
const canGoBack = useCanGoBack();
2633
const bookQuery = useQuery(
2734
orpc.book.getById.queryOptions({ input: { id: props.params.id } })
2835
);
@@ -39,15 +46,65 @@ export const PageBook = (props: { params: { id: string } }) => {
3946
return set('default', { book: bookQuery.data });
4047
});
4148

49+
const deleteBook = async () => {
50+
try {
51+
await orpc.book.deleteById.call({ id: props.params.id });
52+
await Promise.all([
53+
// Invalidate books list
54+
queryClient.invalidateQueries({
55+
queryKey: orpc.book.getAll.key(),
56+
type: 'all',
57+
}),
58+
// Remove user from cache
59+
queryClient.removeQueries({
60+
queryKey: orpc.book.getById.key({ input: { id: props.params.id } }),
61+
}),
62+
]);
63+
64+
toast.success('Book deleted');
65+
66+
// Redirect
67+
if (canGoBack) {
68+
router.history.back();
69+
} else {
70+
router.navigate({ to: '..', replace: true });
71+
}
72+
} catch {
73+
toast.error('Failed to book the user');
74+
}
75+
};
76+
4277
return (
4378
<PageLayout>
4479
<PageLayoutTopBar
4580
backButton={<BackButton />}
4681
actions={
4782
<>
48-
<ResponsiveIconButton variant="ghost" label="Delete">
49-
<Trash2Icon />
50-
</ResponsiveIconButton>
83+
<WithPermissions
84+
permissions={[
85+
{
86+
book: ['delete'],
87+
},
88+
]}
89+
>
90+
<ConfirmResponsiveDrawer
91+
onConfirm={() => deleteBook()}
92+
title={`Delete ${bookQuery.data?.title}`}
93+
description={
94+
<>
95+
You are about to permanently delete this book.{' '}
96+
<strong>This action cannot be undone.</strong> Please
97+
confirm your decision carefully.
98+
</>
99+
}
100+
confirmText="Delete"
101+
confirmVariant="destructive"
102+
>
103+
<ResponsiveIconButton variant="ghost" label="Delete" size="sm">
104+
<Trash2Icon />
105+
</ResponsiveIconButton>
106+
</ConfirmResponsiveDrawer>
107+
</WithPermissions>
51108
<Button asChild size="sm" variant="secondary">
52109
<Link
53110
to="/manager/books/$id/update"

app/features/user/manager/page-user.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export const PageUser = (props: { params: { id: string } }) => {
6161

6262
const deleteUser = async () => {
6363
try {
64-
await orpc.user.delete.call({ id: props.params.id });
64+
await orpc.user.deleteById.call({ id: props.params.id });
6565
await Promise.all([
6666
// Invalidate users list
6767
queryClient.invalidateQueries({
@@ -74,6 +74,8 @@ export const PageUser = (props: { params: { id: string } }) => {
7474
}),
7575
]);
7676

77+
toast.success('User deleted');
78+
7779
// Redirect
7880
if (canGoBack) {
7981
router.history.back();

app/server/routers/book.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,4 +195,31 @@ export default {
195195
throw new ORPCError('INTERNAL_SERVER_ERROR');
196196
}
197197
}),
198+
199+
deleteById: protectedProcedure({
200+
permission: {
201+
user: ['delete'],
202+
},
203+
})
204+
.route({
205+
method: 'DELETE',
206+
path: '/books/{id}',
207+
tags,
208+
})
209+
.input(
210+
zBook().pick({
211+
id: true,
212+
})
213+
)
214+
.output(z.void())
215+
.handler(async ({ context, input }) => {
216+
context.logger.info('Delete book');
217+
try {
218+
await context.db.book.delete({
219+
where: { id: input.id },
220+
});
221+
} catch {
222+
throw new ORPCError('INTERNAL_SERVER_ERROR');
223+
}
224+
}),
198225
};

app/server/routers/user.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ export default {
204204
}
205205
}),
206206

207-
delete: protectedProcedure({
207+
deleteById: protectedProcedure({
208208
permission: {
209209
user: ['delete'],
210210
},

0 commit comments

Comments
 (0)