Skip to content

Commit

Permalink
create offer
Browse files Browse the repository at this point in the history
  • Loading branch information
Felix-Asante committed Dec 31, 2023
1 parent 9e9e3e4 commit 9a0112c
Show file tree
Hide file tree
Showing 24 changed files with 938 additions and 18 deletions.
Binary file modified bun.lockb
Binary file not shown.
32 changes: 16 additions & 16 deletions components.json
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"css": "app/globals.css",
"baseColor": "slate",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/utils/helpers"
}
}
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"css": "src/app/globals.css",
"baseColor": "slate",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/utils/helpers"
}
}
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,20 @@
"@hookform/resolvers": "^3.3.2",
"@nextui-org/react": "^2.2.9",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-popover": "^1.0.7",
"@radix-ui/react-slot": "^1.0.2",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"date-fns": "^3.0.6",
"framer-motion": "^10.16.5",
"lucide-react": "^0.302.0",
"next": "14.0.3",
"next-auth": "^4.24.5",
"react": "^18",
"react-day-picker": "^8.10.0",
"react-dom": "^18",
"react-hook-form": "^7.48.2",
"react-select-async-paginate": "^0.7.3",
"sonner": "^1.2.3",
"tailwind-merge": "^2.2.0",
"tailwindcss-animate": "^1.0.7",
Expand Down
48 changes: 48 additions & 0 deletions src/actions/specials.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"use server";

import { apiConfig } from "@/lib/apiConfig";
import { apiHandler } from "@/lib/apiHandler";
import { ResponseMeta, SeverActionResponse } from "@/types";
import { CreateOfferDto } from "@/types/dtos/offers";
import { Special } from "@/types/specials";
import { Query } from "@/types/url";
import { getErrorMessage } from "@/utils/helpers";
import { Tags } from "@/utils/tags";
import { revalidateTag } from "next/cache";

export interface GetOffersResponse {
meta: ResponseMeta;
items: Special[];
}
export async function getOffers(
query: Query,
): Promise<SeverActionResponse<GetOffersResponse>> {
try {
const endpoint = apiConfig.special_offers.list(query);
const offers = await apiHandler<GetOffersResponse>({
endpoint,
method: "GET",
next: { tags: [Tags.offers] },
});
return { results: offers };
} catch (error) {
return { error: getErrorMessage(error) };
}
}
export async function createOffer(body: CreateOfferDto) {
try {
const endpoint = apiConfig.special_offers.create();
await apiHandler({ endpoint, method: "POST", body });
} catch (error) {
throw new Error(getErrorMessage(error));
}
}
export async function deleteOffer(offerId: string) {
try {
const endpoint = apiConfig.special_offers.delete(offerId);
await apiHandler<Special[]>({ endpoint, method: "DELETE" });
revalidateTag(Tags.offers);
} catch (error) {
throw new Error(getErrorMessage(error));
}
}
110 changes: 110 additions & 0 deletions src/app/(dashboard)/specials/_sections/CreateOfferContent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { DatePicker } from "@/components/shared/input/DatePicker";
import InfiniteScrollSelect from "@/components/shared/input/InfiniteScrollSelectInput";
import TextField from "@/components/shared/input/TextField";
import { apiConfig } from "@/lib/apiConfig";
import { Place } from "@/types/place";
import { SpecialType } from "@/types/specials";
import { Textarea } from "@nextui-org/react";
import React from "react";
import { Controller } from "react-hook-form";

interface Props {
control: any;
watch: (field: string) => string;
}
export default function CreateOfferContent({ control, watch }: Props) {
const startDate = watch("start_date");
console.log(startDate);
return (
<div className='grid md:grid-cols-2 gap-5'>
<TextField
control={control}
label='Title'
name='title'
placeholder='Title'
radius='sm'
variant='bordered'
labelPlacement='outside'
/>
<Controller
name='place'
render={({ field }) => (
<InfiniteScrollSelect
url={apiConfig.places.list({})}
{...field}
getOptionLabel={(option: Place) => option?.name}
getOptionValue={(option: Place) => option.id}
isOptionSelected={(option: Place, selectedValue: Place[]) => {
const isSelected = option?.id === selectedValue?.[0]?.id;
return isSelected;
}}
label='Establishment'
/>
)}
control={control}
/>
<Controller
name='type'
render={({ field }) => (
<InfiniteScrollSelect
url={apiConfig.special_offers.list_offer_types()}
getOptionLabel={(option: SpecialType) => option?.name}
getOptionValue={(option: SpecialType) => option?.id}
isOptionSelected={(
option: SpecialType,
selectedValue: SpecialType[],
) => {
const isSelected = option?.id === selectedValue?.[0]?.id;
return isSelected;
}}
label='Offer Type'
{...field}
/>
)}
control={control}
/>

<DatePicker
name='start_date'
control={control}
label='Offer starting date'
minDate={new Date()}
/>
<DatePicker
minDate={new Date()}
name='end_date'
control={control}
label='Offer ending date'
/>
<TextField
control={control}
label='Price'
name='price'
placeholder='Price'
radius='sm'
variant='bordered'
labelPlacement='outside'
/>
<TextField
control={control}
label='Discount'
name='discount'
placeholder='Discount'
radius='sm'
variant='bordered'
labelPlacement='outside'
/>
<Textarea
label='Description'
variant='bordered'
labelPlacement='outside'
placeholder='Add offer description'
disableAnimation
classNames={{
// base: "max-w-xs",
input: "resize-y min-h-[40px]",
}}
/>
</div>
);
}
55 changes: 55 additions & 0 deletions src/app/(dashboard)/specials/_sections/OffersListSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
"use client";
import { GetOffersResponse } from "@/actions/specials";
import HStack from "@/components/shared/layout/HStack";
import { pluralize } from "@/utils/helpers";
import { Button, Pagination } from "@nextui-org/react";
import React from "react";
import OffersTable from "./OffersTable";
import useQueryParams from "@/hooks/useQueryParam";
import { DASHBOARD_PATHS } from "@/config/routes";
import { useRouter } from "next/navigation";

interface Props {
offers: GetOffersResponse;
}
export default function OffersListSection({ offers }: Props) {
const totalPages = offers?.meta?.totalPages;
const totalOffers = offers?.meta?.totalItems;

const { add } = useQueryParams();
const router = useRouter();

return (
<div>
<HStack className='items-center justify-between'>
<div>
<h3 className='font-semibold text-xl'>
{totalOffers} {pluralize("Offer", totalOffers)}
</h3>
<p className='text-gray-400'>List of all Offers</p>
</div>

<Button
onClick={() => router.push(DASHBOARD_PATHS.specials.new())}
radius='sm'
color='primary'
disableRipple
>
Create new offer
</Button>
</HStack>
<OffersTable offers={offers?.items} />
{totalPages > 1 && (
<HStack className='justify-center mt-2'>
<Pagination
total={totalPages}
initialPage={1}
variant='bordered'
showControls
onChange={(page) => add("page", page.toString())}
/>
</HStack>
)}
</div>
);
}
Loading

0 comments on commit 9a0112c

Please sign in to comment.