Skip to content

Commit

Permalink
feat: form functionality added to booking
Browse files Browse the repository at this point in the history
  • Loading branch information
bencodes07 committed May 12, 2024
1 parent a4584d2 commit 68a2c77
Show file tree
Hide file tree
Showing 5 changed files with 450 additions and 6 deletions.
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,11 @@
"@radix-ui/react-alert-dialog": "^1.0.5",
"@radix-ui/react-avatar": "^1.0.4",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-progress": "^1.0.3",
"@radix-ui/react-select": "^2.0.0",
"@radix-ui/react-separator": "^1.0.3",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-toast": "^1.1.5",
Expand All @@ -63,6 +65,7 @@
"class-variance-authority": "^0.7.0",
"classnames": "^2.5.1",
"clsx": "^2.1.0",
"date-fns": "^3.6.0",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-tailwindcss": "^3.14.1",
"framer-motion": "^11.0.3",
Expand All @@ -75,6 +78,7 @@
"next": "14.1.0",
"next-sitemap": "^4.2.3",
"react": "^18",
"react-day-picker": "^8.10.1",
"react-dom": "^18",
"react-hook-form": "^7.50.0",
"react-icons": "^5.0.1",
Expand Down
161 changes: 155 additions & 6 deletions src/app/dashboard/bookings/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,19 @@ import {
AlertDialogTitle,
AlertDialogTrigger
} from "@/components/ui/alert-dialog";
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger
} from "@/components/ui/dialog";
import { Progress } from "@/components/ui/progress";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Calendar } from "@/components/ui/calendar";

function Bookings() {
const [user, setUser] = useState<User | null>();
Expand Down Expand Up @@ -59,6 +72,23 @@ function Bookings() {

const router = useRouter();

const [select, setSelect] = useState("");
const [date, setDate] = useState<Date | undefined>(new Date());

const [bookingMode, setBookingMode] = useState(0);
const [bookingContent, setBookingContent] = useState({
title: "Select your company",
description: "With which company do you want to book this appointment?",
select: {
active: true,
content: ["Github", "Böckmann GmbH", "MeetMate"]
},
calendar: {
active: false
},
progress: 33
});

useEffect(() => {
const fetchUser = async () => {
setLoading(true);
Expand All @@ -76,10 +106,80 @@ function Bookings() {
fetchUser().catch(console.error);
}, []);

const [bookingResult, setBookingResult] = useState<{ company: string; date: Date | undefined; time: string }>({
company: "",
date: new Date(),
time: ""
});

useEffect(() => {
setFilteredEvents(events.filter((event) => event.Subject.toLowerCase().includes(searchQuery.toLowerCase())));
}, [searchQuery, events]);

function updateBookingProgress() {
if (bookingMode === 1 && select !== "") {
setBookingResult({ ...bookingResult, company: select });
setSelect("");
setBookingContent({
title: "Select your Date",
description: "When would you like to book this appointment?",
select: {
active: false,
content: ["13:00 - 14:00", "15:00 - 16:00", "19:00 - 20:30"]
},
calendar: {
active: true
},
progress: 66
});
setBookingMode(bookingMode + 1);
} else if (bookingMode === 2) {
setBookingResult({ ...bookingResult, date });
setBookingContent({
title: "Select your Slot",
description: "At what time would you like to book this appointment?",
select: {
active: true,
content: ["13:00 - 14:00", "15:00 - 16:00", "19:00 - 20:30"]
},
calendar: {
active: false
},
progress: 80
});
setBookingMode(bookingMode + 1);
} else if (bookingMode === 3 && select !== "") {
setBookingResult({ ...bookingResult, time: select });
setBookingContent({
title: "Success",
description: "Your appointment was successfully booked",
select: {
active: false,
content: ["13:00 - 14:00", "15:00 - 16:00", "19:00 - 20:30"]
},
calendar: {
active: false
},
progress: 100
});
console.log(bookingResult);
} else if (bookingMode === 0) {
setBookingContent({
title: "Select your company",
description: "With which company do you want to book this appointment?",
select: {
active: true,
content: ["Github", "Böckmann GmbH", "MeetMate"]
},
calendar: {
active: false
},
progress: 33
});
setBookingMode(1);
}
}

const logout = async () => {
try {
await deleteToken();
Expand Down Expand Up @@ -150,10 +250,10 @@ function Bookings() {
Help
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogContent className={"border-border text-foreground"}>
<AlertDialogHeader>
<AlertDialogTitle>You need help?</AlertDialogTitle>
<AlertDialogDescription className={"flex flex-wrap gap-x-2"}>
<AlertDialogDescription className={"flex flex-wrap gap-2"}>
If you need any help or would like to request a new feature contact{" "}
<a href={"mailto:[email protected]"}>&quot;boeckmannben{"<at>"}gmail.com&quot;</a>.
</AlertDialogDescription>
Expand All @@ -163,10 +263,59 @@ function Bookings() {
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
<Button className={"text-foreground"}>
<FaPlus className={"mr-1"} />
Book Appointment
</Button>
<Dialog>
<DialogTrigger>
<Button
onClick={() => {
setBookingMode(0);
setSelect("");
}}
className={"text-foreground"}>
<FaPlus className={"mr-1"} />
Book Appointment
</Button>
</DialogTrigger>
<DialogContent className={"border-border text-foreground"}>
<DialogHeader className={"gap-y-3"}>
<DialogTitle>{bookingContent.title}</DialogTitle>
<DialogDescription>{bookingContent.description}</DialogDescription>
<Select onValueChange={setSelect}>
<SelectTrigger style={{ display: bookingContent.select.active ? "flex" : "none" }}>
<SelectValue placeholder="Select" />
</SelectTrigger>
<SelectContent className={"border-border"}>
{bookingContent.select.content.map((content, index) => (
<SelectItem key={index} value={content}>
{content}
</SelectItem>
))}
</SelectContent>
</Select>
<Calendar
mode="single"
className="rounded-md"
selected={date}
onSelect={setDate}
style={{ display: bookingContent.calendar.active ? "block" : "none" }}
/>
</DialogHeader>
<DialogFooter>
<div className={"flex w-full flex-col"}>
<div className={"flex justify-end gap-x-4"}>
<DialogClose asChild>
<Button type="button" variant="secondary">
Cancel
</Button>
</DialogClose>
<Button onClick={updateBookingProgress} className={"text-foreground"}>
Next
</Button>
</div>
<Progress className={"mt-3 h-1"} value={bookingContent.progress} />
</div>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
</div>
<div className="mt-4 flex h-fit w-full rounded-[20px] bg-subtle p-6">
Expand Down
56 changes: 56 additions & 0 deletions src/components/ui/calendar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
"use client";

import * as React from "react";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { DayPicker } from "react-day-picker";

import { cn } from "@/lib/utils";
import { buttonVariants } from "@/components/ui/button";

export type CalendarProps = React.ComponentProps<typeof DayPicker>;

function Calendar({ className, classNames, showOutsideDays = true, ...props }: CalendarProps) {
return (
<DayPicker
showOutsideDays={showOutsideDays}
className={cn("p-3", className)}
classNames={{
months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0",
month: "space-y-4",
caption: "flex justify-center pt-1 relative items-center",
caption_label: "text-sm font-medium",
nav: "space-x-1 flex items-center",
nav_button: cn(
buttonVariants({ variant: "outline" }),
"h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100"
),
nav_button_previous: "absolute left-1",
nav_button_next: "absolute right-1",
table: "w-full border-collapse space-y-1",
head_row: "flex",
head_cell: "text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]",
row: "flex w-full mt-2",
cell: "h-9 w-9 text-center text-sm p-0 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20",
day: cn(buttonVariants({ variant: "ghost" }), "h-9 w-9 p-0 font-normal aria-selected:opacity-100"),
day_range_end: "day-range-end",
day_selected:
"bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
day_today: "bg-accent text-accent-foreground",
day_outside:
"day-outside text-muted-foreground opacity-50 aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30",
day_disabled: "text-muted-foreground opacity-50",
day_range_middle: "aria-selected:bg-accent aria-selected:text-accent-foreground",
day_hidden: "invisible",
...classNames
}}
components={{
IconLeft: () => <ChevronLeft className="size-4" />,
IconRight: () => <ChevronRight className="size-4" />
}}
{...props}
/>
);
}
Calendar.displayName = "Calendar";

export { Calendar };
96 changes: 96 additions & 0 deletions src/components/ui/dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
"use client";

import * as React from "react";
import * as DialogPrimitive from "@radix-ui/react-dialog";
import { X } from "lucide-react";

import { cn } from "@/lib/utils";

const Dialog = DialogPrimitive.Root;

const DialogTrigger = DialogPrimitive.Trigger;

const DialogPortal = DialogPrimitive.Portal;

const DialogClose = DialogPrimitive.Close;

const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay
ref={ref}
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
/>
));
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;

const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
className
)}
{...props}>
{children}
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<X className="size-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
));
DialogContent.displayName = DialogPrimitive.Content.displayName;

const DialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div className={cn("flex flex-col space-y-1.5 text-center sm:text-left", className)} {...props} />
);
DialogHeader.displayName = "DialogHeader";

const DialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div className={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)} {...props} />
);
DialogFooter.displayName = "DialogFooter";

const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn("text-lg font-semibold leading-none tracking-tight", className)}
{...props}
/>
));
DialogTitle.displayName = DialogPrimitive.Title.displayName;

const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description ref={ref} className={cn("text-sm text-muted-foreground", className)} {...props} />
));
DialogDescription.displayName = DialogPrimitive.Description.displayName;

export {
Dialog,
DialogPortal,
DialogOverlay,
DialogClose,
DialogTrigger,
DialogContent,
DialogHeader,
DialogFooter,
DialogTitle,
DialogDescription
};
Loading

0 comments on commit 68a2c77

Please sign in to comment.