Skip to content

Commit

Permalink
delete place api integration
Browse files Browse the repository at this point in the history
  • Loading branch information
Felix-Asante committed Dec 9, 2023
1 parent f2d7fb3 commit 55ea1ab
Show file tree
Hide file tree
Showing 9 changed files with 345 additions and 89 deletions.
19 changes: 19 additions & 0 deletions src/actions/place.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
"use server";

import { DASHBOARD_PATHS } from "@/config/routes";
import { apiConfig } from "@/lib/apiConfig";
import { apiHandler } from "@/lib/apiHandler";
import { ResponseMeta } from "@/types";
import { Place } from "@/types/place";
import { Query } from "@/types/url";
import { getErrorMessage } from "@/utils/helpers";
import { Tags } from "@/utils/tags";
import { revalidateTag } from "next/cache";
import { redirect } from "next/navigation";

interface PlacesResponse {
items: Place[];
Expand All @@ -18,9 +22,24 @@ export async function getPlaces(query: Query): Promise<PlacesResponse> {
const places = await apiHandler<PlacesResponse>({
endpoint,
method: "GET",
next: { tags: [Tags.places] },
});
return places;
} catch (error) {
throw new Error(getErrorMessage(error));
}
}
export async function deletePlace(placeId: string, redirectHome = false) {
try {
const endpoint = apiConfig.places.delete(placeId);
await apiHandler({
endpoint,
method: "DELETE",
});
revalidateTag(Tags.places);
redirectHome && redirect(DASHBOARD_PATHS.places.root);
} catch (error) {
console.log(error);
throw new Error(getErrorMessage(error));
}
}
184 changes: 132 additions & 52 deletions src/app/(dashboard)/places/sections/PlaceTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { DASHBOARD_PATHS } from "@/config/routes";
import { Place } from "@/types/place";
import {
Avatar,
Button,
Table,
TableBody,
TableCell,
Expand All @@ -15,8 +16,13 @@ import {
} from "@nextui-org/react";
import { PencilIcon, TrashIcon } from "lucide-react";
import Link from "next/link";
import React, { useState } from "react";
import React, { useState, useTransition } from "react";
import illustration_empty_content from "../../../../../public/svg/illustration_empty_content.svg";
import Modal from "@/components/shared/modal";
import { toast } from "sonner";
import { getErrorMessage } from "@/utils/helpers";
import { deletePlace } from "@/actions/place";
import { useServerAction } from "@/hooks/useServerAction";

const columns = [
{
Expand All @@ -39,64 +45,138 @@ const columns = [

interface TableProps {
places: Place[];
selectedKeys: any;
onSelectionChange: any;
}
export default function PlaceTable({ places }: TableProps) {
export default function PlaceTable({
places,
selectedKeys,
onSelectionChange,
}: TableProps) {
const [selectionBehavior, setSelectionBehavior] = useState<
"toggle" | "replace" | undefined
>("toggle");

const [placeToBeDeleted, setPlaceToBeDeleted] = useState<string | null>(null);

const [run, { loading }] = useServerAction<any, typeof deletePlace>(
deletePlace,
);

const openModal = placeToBeDeleted != null && placeToBeDeleted?.length > 0;

const closeModal = () => setPlaceToBeDeleted(null);

const deletePlaceHandler = async () => {
try {
if (placeToBeDeleted) {
await run(placeToBeDeleted);
toast.success("Place successfully deleted");
closeModal();
}
} catch (error) {
toast.error(getErrorMessage(error));
}
};
return (
<Table
className='mt-4'
aria-label='list of places'
selectionMode='multiple'
selectionBehavior={selectionBehavior}
shadow='none'
radius='none'
>
<TableHeader>
{columns.map((column) => (
<TableColumn key={column.key}>{column.label}</TableColumn>
))}
<>
<Table
className='mt-4'
aria-label='list of places'
selectionMode='multiple'
selectionBehavior={selectionBehavior}
shadow='none'
radius='none'
selectedKeys={selectedKeys}
onSelectionChange={onSelectionChange}
>
<TableHeader>
{columns.map((column) => (
<TableColumn key={column.key}>{column.label}</TableColumn>
))}

<TableColumn />
</TableHeader>
<TableBody
emptyContent={
<EmptyContent
img={illustration_empty_content}
title=''
description='No places added'
/>
<TableColumn />
</TableHeader>
<TableBody
emptyContent={
<EmptyContent
img={illustration_empty_content}
title=''
description='No places added'
/>
}
>
{places.map((row) => (
<TableRow key={row.id}>
<TableCell>
<HStack className='items-center'>
<Avatar
src={row?.logo}
isBordered
name={row?.name}
size='sm'
/>
<div>
<p>{row?.name}</p>
<p className='text-gray-400'>{row?.email}</p>
</div>
</HStack>
</TableCell>
<TableCell>{row?.address}</TableCell>
<TableCell>{row?.category?.name}</TableCell>
<TableCell>
{DEFAULT_CURRENCY.symbol} {row?.deliveryFee}
</TableCell>
<TableCell>
<HStack className='gap-2 items-center'>
<Link href={DASHBOARD_PATHS.places.edit(row?.slug)}>
<PencilIcon size={20} className='text-success' />
</Link>
<Button
isIconOnly
variant='light'
size='sm'
aria-label='delete'
onClick={(e) => {
e.stopPropagation();
setPlaceToBeDeleted(row.id);
}}
>
<TrashIcon size={20} className='text-danger' />
</Button>
</HStack>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
<Modal
title='Delete place'
description='Please note that this operation cannot be undone'
content={
<HStack className='justify-end gap-2'>
<Button
onClick={closeModal}
variant='bordered'
color='default'
radius='sm'
isDisabled={loading}
>
Cancel
</Button>
<Button
color='primary'
radius='sm'
isLoading={loading}
onClick={deletePlaceHandler}
>
Continue
</Button>
</HStack>
}
>
{places.map((row) => (
<TableRow key={row.id}>
<TableCell>
<HStack className='items-center'>
<Avatar src={row?.logo} isBordered name={row?.name} size='sm' />
<div>
<p>{row?.name}</p>
<p className='text-gray-400'>{row?.email}</p>
</div>
</HStack>
</TableCell>
<TableCell>{row?.address}</TableCell>
<TableCell>{row?.category?.name}</TableCell>
<TableCell>
{DEFAULT_CURRENCY.symbol} {row?.deliveryFee}
</TableCell>
<TableCell>
<HStack>
<Link href={DASHBOARD_PATHS.places.edit(row?.slug)}>
<PencilIcon size={20} className='text-success' />
</Link>
<TrashIcon size={20} className='text-danger' />
</HStack>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
isOpen={openModal}
onClose={closeModal}
/>
</>
);
}
22 changes: 18 additions & 4 deletions src/app/(dashboard)/places/sections/PlaceTableFilters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,18 @@ interface FormFields {
search: string;
category: string;
}
interface Props {
categories: Category[];
disableDelete: boolean;
onDelete: () => void;
deleting: boolean;
}
export default function PlaceTableFilters({
categories,
}: {
categories: Category[];
}) {
disableDelete,
onDelete,
deleting,
}: Props) {
const { control, watch } = useForm<FormFields>();

const search = useDebounce(watch("search"), 1000);
Expand Down Expand Up @@ -73,7 +80,14 @@ export default function PlaceTableFilters({
variant='bordered'
/>
</HStack>
<Button color='danger' disableRipple radius='sm' disabled>
<Button
color='danger'
disableRipple
radius='sm'
isDisabled={disableDelete}
isLoading={deleting}
onClick={onDelete}
>
Delete places
</Button>
</HStack>
Expand Down
48 changes: 44 additions & 4 deletions src/app/(dashboard)/places/sections/PlacesContentSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@ import HStack from "@/components/shared/layout/HStack";
import { DASHBOARD_PATHS } from "@/config/routes";
import { Button, Pagination } from "@nextui-org/react";
import { PlusIcon } from "lucide-react";
import React from "react";
import React, { useState } from "react";
import PlaceTableFilters from "./PlaceTableFilters";
import PlaceTable from "./PlaceTable";
import { Place } from "@/types/place";
import { Category } from "@/types/category";
import { pluralize } from "@/utils/helpers";
import { getErrorMessage, pluralize } from "@/utils/helpers";
import { ResponseMeta } from "@/types";
import useQueryParams from "@/hooks/useQueryParam";
import { useServerAction } from "@/hooks/useServerAction";
import { deletePlace } from "@/actions/place";
import { toast } from "sonner";

interface Props {
places: Place[];
Expand All @@ -25,6 +28,34 @@ export default function PlacesContentSection({
}: Props) {
const totalPlace = meta?.totalItems;
const { add } = useQueryParams();
const [selectedPlaces, setSelectedPlaces] = useState<Set<string>>(
new Set([]),
);

const [run, { loading }] = useServerAction<any, typeof deletePlace>(
deletePlace,
);

const deleteMorePlaceHandler = async () => {
let placeIds: string[] = [];
try {
if (typeof selectedPlaces === "string") {
placeIds = places?.map((place) => place.id);
} else {
placeIds = [...selectedPlaces];
}
if (placeIds.length === 0) {
toast.error("Make sure you have selected a place");
return;
}
await Promise.all(placeIds.map((placeId) => run(placeId)));
setSelectedPlaces(new Set([]));
toast.success("Places successfully deleted");
} catch (error) {
toast.error(getErrorMessage(error));
}
};

return (
<>
<HStack className='items-center justify-between'>
Expand All @@ -42,8 +73,17 @@ export default function PlacesContentSection({
</Button>
</HStack>
<div className='mt-3 border rounded-lg p-4'>
<PlaceTableFilters categories={categories} />
<PlaceTable places={places} />
<PlaceTableFilters
categories={categories}
disableDelete={selectedPlaces.size <= 0}
deleting={loading}
onDelete={deleteMorePlaceHandler}
/>
<PlaceTable
places={places}
selectedKeys={selectedPlaces}
onSelectionChange={setSelectedPlaces}
/>
{meta?.totalPages > 1 && (
<HStack className='justify-center mt-2'>
<Pagination
Expand Down
Loading

0 comments on commit 55ea1ab

Please sign in to comment.