@@ -75,12 +93,21 @@ export default function BookingForm({ booking }: { booking?: Booking | null }) {
-
+
setSelectedTime(undefined)} />
+ {watchDate && (
+
+ )}
-
-
diff --git a/suncityla/app/booking/components/TimeDatePicker/TimeDatePicker.tsx b/suncityla/app/booking/components/TimeDatePicker/TimeDatePicker.tsx
index 4505cd7..462aff7 100644
--- a/suncityla/app/booking/components/TimeDatePicker/TimeDatePicker.tsx
+++ b/suncityla/app/booking/components/TimeDatePicker/TimeDatePicker.tsx
@@ -1,25 +1,21 @@
-"use client";
+'use client';
-import { useState } from "react";
-import { format } from "date-fns";
-import { Calendar as CalendarIcon } from "lucide-react";
-import { cn } from "@/lib/utils";
-import { Button } from "@/components/ui/button";
-import { Calendar } from "@/components/ui/calendar";
-import {
- Popover,
- PopoverContent,
- PopoverTrigger,
-} from "@/components/ui/popover";
+import { useState } from 'react';
+import { format } from 'date-fns';
+import { Calendar as CalendarIcon } from 'lucide-react';
+import { cn } from '@/lib/utils';
+import { Button } from '@/components/ui/button';
+import { Calendar } from '@/components/ui/calendar';
+import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import {
FormControl,
FormDescription,
FormField,
FormItem,
FormMessage,
-} from "@/components/ui/form";
+} from '@/components/ui/form';
-export function DateTimePickerForm() {
+export function DateTimePickerForm({ onChange }: { onChange: () => void }) {
const [open, setOpen] = useState(false);
return (
@@ -34,16 +30,12 @@ export function DateTimePickerForm() {
onClick={() => setOpen(true)}
variant="outline"
className={cn(
- "w-full justify-start font-normal",
- !field.value && "text-muted-foreground"
+ 'w-full justify-start font-normal',
+ !field.value && 'text-muted-foreground',
)}
>
- {field.value ? (
- format(field.value, "PPP")
- ) : (
- Pick a date
- )}
+ {field.value ? format(field.value, 'PPP') : Pick a date}
@@ -54,17 +46,12 @@ export function DateTimePickerForm() {
onSelect={(date) => {
field.onChange(date?.toISOString());
setOpen(false);
+ onChange();
}}
- initialFocus
/>
-
+
diff --git a/suncityla/app/booking/components/TimeSelect/TimeSelect.tsx b/suncityla/app/booking/components/TimeSelect/TimeSelect.tsx
new file mode 100644
index 0000000..7a41e70
--- /dev/null
+++ b/suncityla/app/booking/components/TimeSelect/TimeSelect.tsx
@@ -0,0 +1,59 @@
+'use client';
+
+import * as React from 'react';
+
+import {
+ Select,
+ SelectContent,
+ SelectGroup,
+ SelectItem,
+ SelectLabel,
+ SelectTrigger,
+ SelectValue,
+} from '@/components/ui/select';
+import { FormField, FormItem } from '@/components/ui/form';
+import { format } from 'date-fns';
+
+export function TimeSelect({
+ availableTimes,
+ selectedTime,
+ onTimeSelect,
+}: {
+ availableTimes: { from: number; to: number }[];
+ selectedTime?: number;
+ onTimeSelect: (time: number) => void;
+}) {
+ return (
+ {
+ return (
+
+
+
+ );
+ }}
+ />
+ );
+}
+
+export default TimeSelect;
diff --git a/suncityla/app/booking/components/TimeSelect/times.ts b/suncityla/app/booking/components/TimeSelect/times.ts
new file mode 100644
index 0000000..fcf93d5
--- /dev/null
+++ b/suncityla/app/booking/components/TimeSelect/times.ts
@@ -0,0 +1,12 @@
+const createAvailableTimes = (date: Date) => {
+ const availableTimes = [];
+ for (let i = 8; i < 20; i++) {
+ availableTimes.push({
+ from: date.setHours(i, 0, 0, 0),
+ to: date.setHours(i + 1, 0, 0, 0),
+ });
+ }
+ return availableTimes;
+};
+
+export default createAvailableTimes;
diff --git a/suncityla/components/ui/calendar.tsx b/suncityla/components/ui/calendar.tsx
index d465dc8..037ce79 100644
--- a/suncityla/components/ui/calendar.tsx
+++ b/suncityla/components/ui/calendar.tsx
@@ -1,67 +1,60 @@
-"use client";
+'use client';
-import * as React from "react";
-import { ChevronLeft, ChevronRight } from "lucide-react";
-import { DayPicker } from "react-day-picker";
+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";
+import { cn } from '@/lib/utils';
+import { buttonVariants } from '@/components/ui/button';
export type CalendarProps = React.ComponentProps;
-function Calendar({
- className,
- classNames,
- showOutsideDays = true,
- ...props
-}: CalendarProps) {
+function Calendar({ className, classNames, showOutsideDays = true, ...props }: CalendarProps) {
return (
.day-range-end)]:rounded-r-md [&:has(>.day-range-start)]:rounded-l-md first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md"
- : "[&:has([aria-selected])]:rounded-md"
+ 'relative p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([aria-selected])]:bg-accent [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected].day-range-end)]:rounded-r-md',
+ props.mode === 'range'
+ ? '[&:has(>.day-range-end)]:rounded-r-md [&:has(>.day-range-start)]:rounded-l-md first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md'
+ : '[&:has([aria-selected])]:rounded-md',
),
day: cn(
- buttonVariants({ variant: "ghost" }),
- "h-8 w-8 p-0 font-normal aria-selected:opacity-100"
+ buttonVariants({ variant: 'ghost' }),
+ 'h-8 w-8 p-0 font-normal aria-selected:opacity-100',
),
- day_range_start: "day-range-start",
- day_range_end: "day-range-end",
+ day_range_start: 'day-range-start',
+ 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",
+ '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 aria-selected:bg-accent/50 aria-selected:text-muted-foreground",
- day_disabled: "text-muted-foreground opacity-50",
- day_range_middle:
- "aria-selected:bg-accent aria-selected:text-accent-foreground",
- day_hidden: "invisible",
+ 'day-outside text-muted-foreground aria-selected:bg-accent/50 aria-selected:text-muted-foreground',
+ day_disabled: 'text-muted-foreground opacity-50',
+ day_range_middle: 'aria-selected:bg-accent aria-selected:text-accent-foreground',
+ day_hidden: 'invisible',
...classNames,
}}
components={{
Chevron(props) {
- if (props.orientation === "left") {
+ if (props.orientation === 'left') {
return ;
}
return ;
@@ -71,6 +64,6 @@ function Calendar({
/>
);
}
-Calendar.displayName = "Calendar";
+Calendar.displayName = 'Calendar';
export { Calendar };
diff --git a/suncityla/components/ui/select.tsx b/suncityla/components/ui/select.tsx
new file mode 100644
index 0000000..98d83d2
--- /dev/null
+++ b/suncityla/components/ui/select.tsx
@@ -0,0 +1,152 @@
+'use client';
+
+import * as React from 'react';
+import * as SelectPrimitive from '@radix-ui/react-select';
+import { Check, ChevronDown, ChevronUp } from 'lucide-react';
+
+import { cn } from '@/lib/utils';
+
+const Select = SelectPrimitive.Root;
+
+const SelectGroup = SelectPrimitive.Group;
+
+const SelectValue = SelectPrimitive.Value;
+
+const SelectTrigger = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+ span]:line-clamp-1 dark:border-zinc-800 dark:ring-offset-zinc-950 dark:placeholder:text-zinc-400 dark:focus:ring-zinc-300',
+ className,
+ )}
+ {...props}
+ >
+ {children}
+
+
+
+
+));
+SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
+
+const SelectScrollUpButton = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
+));
+SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
+
+const SelectScrollDownButton = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
+));
+SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName;
+
+const SelectContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, position = 'popper', ...props }, ref) => (
+
+
+
+
+ {children}
+
+
+
+
+));
+SelectContent.displayName = SelectPrimitive.Content.displayName;
+
+const SelectLabel = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+SelectLabel.displayName = SelectPrimitive.Label.displayName;
+
+const SelectItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+
+
+
+
+
+ {children}
+
+));
+SelectItem.displayName = SelectPrimitive.Item.displayName;
+
+const SelectSeparator = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
+
+export {
+ Select,
+ SelectGroup,
+ SelectValue,
+ SelectTrigger,
+ SelectContent,
+ SelectLabel,
+ SelectItem,
+ SelectSeparator,
+ SelectScrollUpButton,
+ SelectScrollDownButton,
+};
diff --git a/suncityla/package-lock.json b/suncityla/package-lock.json
index b01e9b2..3cd088a 100644
--- a/suncityla/package-lock.json
+++ b/suncityla/package-lock.json
@@ -16,6 +16,7 @@
"@radix-ui/react-dropdown-menu": "^2.1.2",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-popover": "^1.1.2",
+ "@radix-ui/react-select": "^2.1.2",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-toast": "^1.2.2",
"@tanstack/react-table": "^8.20.5",
@@ -2465,6 +2466,11 @@
"@prisma/debug": "5.22.0"
}
},
+ "node_modules/@radix-ui/number": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.0.tgz",
+ "integrity": "sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ=="
+ },
"node_modules/@radix-ui/primitive": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz",
@@ -3021,6 +3027,48 @@
}
}
},
+ "node_modules/@radix-ui/react-select": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.1.2.tgz",
+ "integrity": "sha512-rZJtWmorC7dFRi0owDmoijm6nSJH1tVw64QGiNIZ9PNLyBDtG+iAq+XGsya052At4BfarzY/Dhv9wrrUr6IMZA==",
+ "dependencies": {
+ "@radix-ui/number": "1.1.0",
+ "@radix-ui/primitive": "1.1.0",
+ "@radix-ui/react-collection": "1.1.0",
+ "@radix-ui/react-compose-refs": "1.1.0",
+ "@radix-ui/react-context": "1.1.1",
+ "@radix-ui/react-direction": "1.1.0",
+ "@radix-ui/react-dismissable-layer": "1.1.1",
+ "@radix-ui/react-focus-guards": "1.1.1",
+ "@radix-ui/react-focus-scope": "1.1.0",
+ "@radix-ui/react-id": "1.1.0",
+ "@radix-ui/react-popper": "1.2.0",
+ "@radix-ui/react-portal": "1.1.2",
+ "@radix-ui/react-primitive": "2.0.0",
+ "@radix-ui/react-slot": "1.1.0",
+ "@radix-ui/react-use-callback-ref": "1.1.0",
+ "@radix-ui/react-use-controllable-state": "1.1.0",
+ "@radix-ui/react-use-layout-effect": "1.1.0",
+ "@radix-ui/react-use-previous": "1.1.0",
+ "@radix-ui/react-visually-hidden": "1.1.0",
+ "aria-hidden": "^1.1.1",
+ "react-remove-scroll": "2.6.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-slot": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz",
@@ -3138,6 +3186,20 @@
}
}
},
+ "node_modules/@radix-ui/react-use-previous": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.0.tgz",
+ "integrity": "sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og==",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-use-rect": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.0.tgz",
diff --git a/suncityla/package.json b/suncityla/package.json
index 2333ff4..5d4e01f 100644
--- a/suncityla/package.json
+++ b/suncityla/package.json
@@ -29,6 +29,7 @@
"@radix-ui/react-dropdown-menu": "^2.1.2",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-popover": "^1.1.2",
+ "@radix-ui/react-select": "^2.1.2",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-toast": "^1.2.2",
"@tanstack/react-table": "^8.20.5",
diff --git a/suncityla/prisma/migrations/20241206115210_booking_time/migration.sql b/suncityla/prisma/migrations/20241206115210_booking_time/migration.sql
new file mode 100644
index 0000000..ddd0f41
--- /dev/null
+++ b/suncityla/prisma/migrations/20241206115210_booking_time/migration.sql
@@ -0,0 +1,2 @@
+-- AlterTable
+ALTER TABLE "Booking" ADD COLUMN "bookingTime" TEXT;
diff --git a/suncityla/prisma/migrations/20241206173508_remove_bookingtime_column/migration.sql b/suncityla/prisma/migrations/20241206173508_remove_bookingtime_column/migration.sql
new file mode 100644
index 0000000..f0d702d
--- /dev/null
+++ b/suncityla/prisma/migrations/20241206173508_remove_bookingtime_column/migration.sql
@@ -0,0 +1,8 @@
+/*
+ Warnings:
+
+ - You are about to drop the column `bookingTime` on the `Booking` table. All the data in the column will be lost.
+
+*/
+-- AlterTable
+ALTER TABLE "Booking" DROP COLUMN "bookingTime";
diff --git a/suncityla/prisma/schema.prisma b/suncityla/prisma/schema.prisma
index 8b00fc2..0f1eb63 100644
--- a/suncityla/prisma/schema.prisma
+++ b/suncityla/prisma/schema.prisma
@@ -1,10 +1,10 @@
datasource db {
- provider = "postgresql"
- url = env("DATABASE_URL")
+ provider = "postgresql"
+ url = env("DATABASE_URL")
}
generator client {
- provider = "prisma-client-js"
+ provider = "prisma-client-js"
}
// This is an example schema. You can use it as a starting point to create your own schema.
@@ -17,21 +17,21 @@ generator client {
// added this to ensure authentication with next-auth and github
model User {
id String @id @default(cuid())
- username String? @unique
+ username String? @unique
password String?
name String?
- email String? @unique
+ email String? @unique
emailVerified DateTime?
image String?
accounts Account[]
sessions Session[]
// Optional for WebAuthn support
Authenticator Authenticator[]
-
+
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
-
+
model Account {
userId String
type String
@@ -44,33 +44,33 @@ model Account {
scope String?
id_token String?
session_state String?
-
+
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
-
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
-
+
@@id([provider, providerAccountId])
}
-
+
model Session {
sessionToken String @unique
userId String
expires DateTime
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
-
+
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
-
+
model VerificationToken {
identifier String
token String
expires DateTime
-
+
@@id([identifier, token])
}
-
+
// Optional for WebAuthn support
model Authenticator {
credentialID String @unique
@@ -81,26 +81,26 @@ model Authenticator {
credentialDeviceType String
credentialBackedUp Boolean
transports String?
-
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
-
+
@@id([userId, credentialID])
}
model Booking {
- id String @id @unique @default(uuid())
- firstname String
- lastname String
- streetAddress String
- postalCode String
- state String
- bookingDate DateTime
- status BookingStatus @default(PENDING)
+ id String @id @unique @default(uuid())
+ firstname String
+ lastname String
+ streetAddress String
+ postalCode String
+ state String
+ bookingDate DateTime
+ status BookingStatus @default(PENDING)
}
enum BookingStatus {
- PENDING
- CONFIRMED
- CANCELLED
- VISITED
+ PENDING
+ CONFIRMED
+ CANCELLED
+ VISITED
}