Skip to content

Commit 8c50a14

Browse files
committed
web 2 changes
1 parent 2e4711c commit 8c50a14

File tree

8 files changed

+780
-327
lines changed

8 files changed

+780
-327
lines changed

apps/web-v2/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
"next-themes": "^0.4.6",
6161
"nuqs": "^2.4.1",
6262
"react": "^19.0.0",
63+
"react-day-picker": "^9.9.0",
6364
"react-dom": "^19.0.0",
6465
"react-hook-form": "^7.56.3",
6566
"react-markdown": "^10.1.0",
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import * as React from "react";
2+
import { ChevronDown, Calendar as CalendarIcon } from "lucide-react";
3+
4+
import { cn } from "@/lib/utils";
5+
import { Button } from "@/components/ui/button";
6+
import {
7+
Popover,
8+
PopoverContent,
9+
PopoverTrigger,
10+
} from "@/components/ui/popover";
11+
import { DateTimePicker } from "@/components/ui/date-time-picker";
12+
13+
interface AcceptScheduleButtonProps {
14+
onAccept: () => void;
15+
onSchedule: (scheduledDate: Date) => void;
16+
acceptDisabled?: boolean;
17+
scheduleDisabled?: boolean;
18+
className?: string;
19+
acceptText?: string;
20+
scheduleText?: string;
21+
}
22+
23+
export function AcceptScheduleButton({
24+
onAccept,
25+
onSchedule,
26+
acceptDisabled = false,
27+
scheduleDisabled = false,
28+
className,
29+
acceptText = "Accept",
30+
scheduleText = "Schedule",
31+
}: AcceptScheduleButtonProps) {
32+
const [scheduledDate, setScheduledDate] = React.useState<Date>();
33+
const [isScheduleOpen, setIsScheduleOpen] = React.useState(false);
34+
35+
const handleScheduleConfirm = () => {
36+
if (scheduledDate) {
37+
onSchedule(scheduledDate);
38+
setIsScheduleOpen(false);
39+
setScheduledDate(undefined);
40+
}
41+
};
42+
43+
return (
44+
<div className={cn("flex items-center", className)}>
45+
{/* Accept Button */}
46+
<Button
47+
onClick={onAccept}
48+
disabled={acceptDisabled}
49+
className="rounded-r-none border-r-0 bg-[#2F6868] font-medium text-white hover:bg-[#2F6868]/90"
50+
size="sm"
51+
>
52+
{acceptText}
53+
</Button>
54+
55+
{/* Schedule Dropdown */}
56+
<Popover
57+
open={isScheduleOpen}
58+
onOpenChange={setIsScheduleOpen}
59+
>
60+
<PopoverTrigger asChild>
61+
<Button
62+
variant="outline"
63+
disabled={scheduleDisabled}
64+
className="rounded-l-none border-l-0 bg-white px-2 font-medium text-gray-700 hover:bg-gray-50"
65+
size="sm"
66+
>
67+
<ChevronDown className="h-4 w-4" />
68+
</Button>
69+
</PopoverTrigger>
70+
<PopoverContent
71+
className="w-auto p-0"
72+
align="end"
73+
>
74+
<div className="space-y-4 p-4">
75+
<div className="flex items-center gap-2">
76+
<CalendarIcon className="h-4 w-4" />
77+
<h4 className="text-sm font-medium">Schedule for later</h4>
78+
</div>
79+
<DateTimePicker
80+
date={scheduledDate}
81+
setDate={setScheduledDate}
82+
placeholder="Select date and time"
83+
className="w-full"
84+
/>
85+
<div className="flex justify-end gap-2">
86+
<Button
87+
variant="outline"
88+
size="sm"
89+
onClick={() => {
90+
setIsScheduleOpen(false);
91+
setScheduledDate(undefined);
92+
}}
93+
>
94+
Cancel
95+
</Button>
96+
<Button
97+
size="sm"
98+
onClick={handleScheduleConfirm}
99+
disabled={!scheduledDate}
100+
className="bg-[#2F6868] text-white hover:bg-[#2F6868]/90"
101+
>
102+
{scheduleText}
103+
</Button>
104+
</div>
105+
</div>
106+
</PopoverContent>
107+
</Popover>
108+
</div>
109+
);
110+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import * as React from "react";
2+
import { ChevronLeft, ChevronRight } from "lucide-react";
3+
import { DayPicker } from "react-day-picker";
4+
5+
import { cn } from "@/lib/utils";
6+
import { buttonVariants } from "@/components/ui/button";
7+
8+
export type CalendarProps = React.ComponentProps<typeof DayPicker>;
9+
10+
function Calendar({
11+
className,
12+
classNames,
13+
showOutsideDays = true,
14+
...props
15+
}: CalendarProps) {
16+
return (
17+
<DayPicker
18+
showOutsideDays={showOutsideDays}
19+
className={cn("p-3", className)}
20+
classNames={{
21+
months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0",
22+
month: "space-y-4",
23+
caption: "flex justify-center pt-1 relative items-center",
24+
caption_label: "text-sm font-medium",
25+
nav: "space-x-1 flex items-center",
26+
nav_button: cn(
27+
buttonVariants({ variant: "outline" }),
28+
"h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100",
29+
),
30+
nav_button_previous: "absolute left-1",
31+
nav_button_next: "absolute right-1",
32+
table: "w-full border-collapse space-y-1",
33+
head_row: "flex",
34+
head_cell:
35+
"text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]",
36+
row: "flex w-full mt-2",
37+
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",
38+
day: cn(
39+
buttonVariants({ variant: "ghost" }),
40+
"h-9 w-9 p-0 font-normal aria-selected:opacity-100",
41+
),
42+
day_range_end: "day-range-end",
43+
day_selected:
44+
"bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
45+
day_today: "bg-accent text-accent-foreground",
46+
day_outside:
47+
"day-outside text-muted-foreground opacity-50 aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30",
48+
day_disabled: "text-muted-foreground opacity-50",
49+
day_range_middle:
50+
"aria-selected:bg-accent aria-selected:text-accent-foreground",
51+
day_hidden: "invisible",
52+
...classNames,
53+
}}
54+
components={{
55+
IconLeft: ({ ...props }) => <ChevronLeft className="h-4 w-4" />,
56+
IconRight: ({ ...props }) => <ChevronRight className="h-4 w-4" />,
57+
}}
58+
{...props}
59+
/>
60+
);
61+
}
62+
Calendar.displayName = "Calendar";
63+
64+
export { Calendar };
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import * as React from "react";
2+
import { format } from "date-fns";
3+
import { Calendar as CalendarIcon, Clock } from "lucide-react";
4+
5+
import { cn } from "@/lib/utils";
6+
import { Button } from "@/components/ui/button";
7+
import { Calendar } from "@/components/ui/calendar";
8+
import {
9+
Popover,
10+
PopoverContent,
11+
PopoverTrigger,
12+
} from "@/components/ui/popover";
13+
import { Input } from "@/components/ui/input";
14+
import { Label } from "@/components/ui/label";
15+
16+
interface DateTimePickerProps {
17+
date?: Date;
18+
setDate: (date: Date | undefined) => void;
19+
placeholder?: string;
20+
className?: string;
21+
}
22+
23+
export function DateTimePicker({
24+
date,
25+
setDate,
26+
placeholder = "Pick a date and time",
27+
className,
28+
}: DateTimePickerProps) {
29+
const [time, setTime] = React.useState<string>("10:30");
30+
const [isOpen, setIsOpen] = React.useState(false);
31+
32+
const handleDateSelect = (selectedDate: Date | undefined) => {
33+
if (selectedDate) {
34+
const [hours, minutes] = time.split(":");
35+
const newDate = new Date(selectedDate);
36+
newDate.setHours(parseInt(hours, 10));
37+
newDate.setMinutes(parseInt(minutes, 10));
38+
setDate(newDate);
39+
} else {
40+
setDate(undefined);
41+
}
42+
};
43+
44+
const handleTimeChange = (newTime: string) => {
45+
setTime(newTime);
46+
if (date) {
47+
const [hours, minutes] = newTime.split(":");
48+
const newDate = new Date(date);
49+
newDate.setHours(parseInt(hours, 10));
50+
newDate.setMinutes(parseInt(minutes, 10));
51+
setDate(newDate);
52+
}
53+
};
54+
55+
const handleConfirm = () => {
56+
if (date) {
57+
const [hours, minutes] = time.split(":");
58+
const newDate = new Date(date);
59+
newDate.setHours(parseInt(hours, 10));
60+
newDate.setMinutes(parseInt(minutes, 10));
61+
setDate(newDate);
62+
}
63+
setIsOpen(false);
64+
};
65+
66+
React.useEffect(() => {
67+
if (date) {
68+
const hours = date.getHours().toString().padStart(2, "0");
69+
const minutes = date.getMinutes().toString().padStart(2, "0");
70+
setTime(`${hours}:${minutes}`);
71+
}
72+
}, [date]);
73+
74+
return (
75+
<Popover
76+
open={isOpen}
77+
onOpenChange={setIsOpen}
78+
>
79+
<PopoverTrigger asChild>
80+
<Button
81+
variant={"outline"}
82+
className={cn(
83+
"w-[280px] justify-start text-left font-normal",
84+
!date && "text-muted-foreground",
85+
className,
86+
)}
87+
>
88+
<CalendarIcon className="mr-2 h-4 w-4" />
89+
{date ? format(date, "PPP 'at' p") : <span>{placeholder}</span>}
90+
</Button>
91+
</PopoverTrigger>
92+
<PopoverContent
93+
className="w-auto p-0"
94+
align="start"
95+
>
96+
<div className="flex">
97+
<Calendar
98+
mode="single"
99+
selected={date}
100+
onSelect={handleDateSelect}
101+
initialFocus
102+
disabled={(date) => date < new Date().setHours(0, 0, 0, 0)}
103+
/>
104+
<div className="flex flex-col gap-2 border-l px-3 py-4">
105+
<Label
106+
htmlFor="time"
107+
className="text-sm font-medium"
108+
>
109+
Time
110+
</Label>
111+
<div className="flex items-center gap-2">
112+
<Clock className="text-muted-foreground h-4 w-4" />
113+
<Input
114+
id="time"
115+
type="time"
116+
value={time}
117+
onChange={(e) => handleTimeChange(e.target.value)}
118+
className="w-24"
119+
/>
120+
</div>
121+
<Button
122+
size="sm"
123+
onClick={handleConfirm}
124+
className="mt-2"
125+
>
126+
Confirm
127+
</Button>
128+
</div>
129+
</div>
130+
</PopoverContent>
131+
</Popover>
132+
);
133+
}

0 commit comments

Comments
 (0)