Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 32 additions & 15 deletions src/app/(loading-group)/user-settings/PatManagementSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { Input } from "@/components/ui/input";
import usePersonalAccessToken from "@/hooks/usePersonalAccessToken";
import { toast } from "@/lib/toast";
import { getLogoutUrl } from "@/server/actions/logout";
import { addYears } from "date-fns";
import { KeyRoundIcon, ShieldCheckIcon } from "lucide-react";
import Link from "next/link";
import type { FunctionComponent } from "react";
Expand Down Expand Up @@ -192,21 +193,37 @@ const PatManagementSection: FunctionComponent = () => {
</FormSection>

<FormSection step={4} title="Expiry date" last>
<div className="flex items-center gap-3">
<DatePicker
date={expiryDate}
onDateChange={setExpiryDate}
label="Pick a date"
/>
{expiryDate && (
<span className="text-xs text-muted-foreground">
Token valid until{" "}
{expiryDate.toLocaleDateString(undefined, {
day: "numeric",
month: "long",
year: "numeric",
})}
</span>
<div className="flex flex-col gap-2">
<div className="flex items-center gap-3">
<DatePicker
date={expiryDate}
onDateChange={setExpiryDate}
/>
{expiryDate &&

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about we do all that validation not on every render but inside the setExpiryDate function? Besides that, currently the validation does not block the network request from happening at all

!(expiryDate > addYears(new Date(), 1)) &&
!(
expiryDate < new Date(new Date().setHours(0, 0, 0, 0))
) && (
<span className="text-xs text-muted-foreground">
Token valid until{" "}
{expiryDate.toLocaleDateString(undefined, {
day: "numeric",
month: "long",
year: "numeric",
})}
</span>
)}
</div>
{expiryDate &&
expiryDate < new Date(new Date().setHours(0, 0, 0, 0)) && (
<p className="text-xs text-destructive">
Expiry date cannot be in the past.
</p>
)}
{expiryDate && expiryDate > addYears(new Date(), 1) && (
<p className="text-xs text-destructive">
Expiry date cannot be more than 1 year in the future.
</p>
)}
</div>
</FormSection>
Expand Down
82 changes: 58 additions & 24 deletions src/components/DatePicker.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
"use client";

import { format } from "date-fns";
import { ChevronDownIcon } from "lucide-react";
import { addYears, format, isValid, parse } from "date-fns";
import { CalendarIcon } from "lucide-react";
import { useEffect, useState } from "react";

import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
import { Input } from "@/components/ui/input";
import {
Popover,
PopoverContent,
Expand All @@ -13,30 +15,62 @@ import {

interface DatePickerProps {
date: Date | undefined;
label: string;
label?: string;
onDateChange: (date: Date | undefined) => void;
}
export function DatePicker({ date, onDateChange, label }: DatePickerProps) {

const DATE_FORMAT = "dd/MM/yyyy";

export function DatePicker({ date, onDateChange }: DatePickerProps) {
const [inputValue, setInputValue] = useState(
date ? format(date, DATE_FORMAT) : "",
);

useEffect(() => {
setInputValue(date ? format(date, DATE_FORMAT) : "");
}, [date]);

const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const val = e.target.value;
setInputValue(val);
if (val === "") {
onDateChange(undefined);
return;
}
const parsed = parse(val, DATE_FORMAT, new Date());
if (isValid(parsed) && val.length === DATE_FORMAT.length) {
onDateChange(parsed);
}
};

return (
<Popover>
<PopoverTrigger asChild>
<Button
variant="outline"
data-empty={!date}
className="w-[212px] justify-between text-left font-normal data-[empty=true]:text-muted-foreground"
>
{date ? format(date, "PPP") : <span>{label}</span>}
<ChevronDownIcon />
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<Calendar
mode="single"
selected={date}
onSelect={onDateChange}
defaultMonth={date}
/>
</PopoverContent>
</Popover>
<div className="flex items-center gap-1">
<Input
value={inputValue}
onChange={handleInputChange}
placeholder={format(new Date(), DATE_FORMAT)}
className="w-[120px] font-normal tabular-nums"
maxLength={10}
/>
<Popover>
<PopoverTrigger asChild>
<Button variant="outline" size="icon" className="h-9 w-9 shrink-0">
<CalendarIcon className="h-4 w-4" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<Calendar
mode="single"
selected={date}
onSelect={onDateChange}
defaultMonth={date ?? new Date()}
disabled={(d) =>
d < new Date(new Date().setHours(0, 0, 0, 0)) ||
d > addYears(new Date(), 1)
}
/>
</PopoverContent>
</Popover>
</div>
);
}
Loading