Let users set 12/24 hour time format (#2002)
This commit is contained in:
parent
2559873b2c
commit
7826a34b00
8 changed files with 53 additions and 5 deletions
|
@ -9,6 +9,7 @@ import { useLocale } from "@lib/hooks/useLocale";
|
|||
import { inferQueryOutput, trpc } from "@lib/trpc";
|
||||
|
||||
import { Dialog, DialogClose, DialogContent, DialogFooter, DialogHeader } from "@components/Dialog";
|
||||
import { useMeQuery } from "@components/Shell";
|
||||
import { TextArea } from "@components/form/fields";
|
||||
import Button from "@components/ui/Button";
|
||||
import TableActions, { ActionType } from "@components/ui/TableActions";
|
||||
|
@ -16,6 +17,9 @@ import TableActions, { ActionType } from "@components/ui/TableActions";
|
|||
type BookingItem = inferQueryOutput<"viewer.bookings">["bookings"][number];
|
||||
|
||||
function BookingListItem(booking: BookingItem) {
|
||||
// Get user so we can determine 12/24 hour format preferences
|
||||
const query = useMeQuery();
|
||||
const user = query.data;
|
||||
const { t, i18n } = useLocale();
|
||||
const utils = trpc.useContext();
|
||||
const [rejectionReason, setRejectionReason] = useState<string>("");
|
||||
|
@ -120,7 +124,8 @@ function BookingListItem(booking: BookingItem) {
|
|||
<td className="hidden whitespace-nowrap py-4 align-top ltr:pl-6 rtl:pr-6 sm:table-cell">
|
||||
<div className="text-sm leading-6 text-gray-900">{startTime}</div>
|
||||
<div className="text-sm text-gray-500">
|
||||
{dayjs(booking.startTime).format("HH:mm")} - {dayjs(booking.endTime).format("HH:mm")}
|
||||
{dayjs(booking.startTime).format(user && user.timeFormat === 12 ? "h:mma" : "HH:mm")} -{" "}
|
||||
{dayjs(booking.endTime).format(user && user.timeFormat === 12 ? "h:mma" : "HH:mm")}
|
||||
</div>
|
||||
</td>
|
||||
<td className={"flex-1 py-4 ltr:pl-4 rtl:pr-4" + (booking.rejected ? " line-through" : "")}>
|
||||
|
|
|
@ -10,6 +10,7 @@ import { weekdayNames } from "@lib/core/i18n/weekday";
|
|||
import { useLocale } from "@lib/hooks/useLocale";
|
||||
import { TimeRange } from "@lib/types/schedule";
|
||||
|
||||
import { useMeQuery } from "@components/Shell";
|
||||
import Button from "@components/ui/Button";
|
||||
import Select from "@components/ui/form/Select";
|
||||
|
||||
|
@ -46,6 +47,10 @@ type TimeRangeFieldProps = {
|
|||
};
|
||||
|
||||
const TimeRangeField = ({ name }: TimeRangeFieldProps) => {
|
||||
// Get user so we can determine 12/24 hour format preferences
|
||||
const query = useMeQuery();
|
||||
const user = query.data;
|
||||
|
||||
// Lazy-loaded options, otherwise adding a field has a noticable redraw delay.
|
||||
const [options, setOptions] = useState<Option[]>([]);
|
||||
const [selected, setSelected] = useState<number | undefined>();
|
||||
|
@ -57,7 +62,9 @@ const TimeRangeField = ({ name }: TimeRangeFieldProps) => {
|
|||
|
||||
const getOption = (time: ConfigType) => ({
|
||||
value: dayjs(time).toDate().valueOf(),
|
||||
label: dayjs(time).utc().format("HH:mm"),
|
||||
label: dayjs(time)
|
||||
.utc()
|
||||
.format(user && user.timeFormat === 12 ? "h:mma" : "HH:mm"),
|
||||
// .toLocaleTimeString(i18n.language, { minute: "numeric", hour: "numeric" }),
|
||||
});
|
||||
|
||||
|
@ -82,7 +89,7 @@ const TimeRangeField = ({ name }: TimeRangeFieldProps) => {
|
|||
handleSelected(value);
|
||||
return (
|
||||
<Select
|
||||
className="w-[6rem]"
|
||||
className="w-30"
|
||||
options={options}
|
||||
onFocus={() => setOptions(timeOptions())}
|
||||
onBlur={() => setOptions([])}
|
||||
|
@ -100,7 +107,7 @@ const TimeRangeField = ({ name }: TimeRangeFieldProps) => {
|
|||
name={`${name}.end`}
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<Select
|
||||
className="w-[6rem]"
|
||||
className="w-30"
|
||||
options={options}
|
||||
onFocus={() => setOptions(timeOptions({ selected }))}
|
||||
onBlur={() => setOptions([])}
|
||||
|
|
|
@ -146,6 +146,11 @@ function SettingsView(props: ComponentProps<typeof Settings> & { localeProp: str
|
|||
{ value: "light", label: t("light") },
|
||||
{ value: "dark", label: t("dark") },
|
||||
];
|
||||
|
||||
const timeFormatOptions = [
|
||||
{ value: 12, label: t("12_hour") },
|
||||
{ value: 24, label: t("24_hour") },
|
||||
];
|
||||
const usernameRef = useRef<HTMLInputElement>(null!);
|
||||
const nameRef = useRef<HTMLInputElement>(null!);
|
||||
const emailRef = useRef<HTMLInputElement>(null!);
|
||||
|
@ -153,6 +158,10 @@ function SettingsView(props: ComponentProps<typeof Settings> & { localeProp: str
|
|||
const avatarRef = useRef<HTMLInputElement>(null!);
|
||||
const hideBrandingRef = useRef<HTMLInputElement>(null!);
|
||||
const [selectedTheme, setSelectedTheme] = useState<typeof themeOptions[number] | undefined>();
|
||||
const [selectedTimeFormat, setSelectedTimeFormat] = useState({
|
||||
value: props.user.timeFormat || 12,
|
||||
label: timeFormatOptions.find((option) => option.value === props.user.timeFormat)?.label || 12,
|
||||
});
|
||||
const [selectedTimeZone, setSelectedTimeZone] = useState<ITimezone>(props.user.timeZone);
|
||||
const [selectedWeekStartDay, setSelectedWeekStartDay] = useState({
|
||||
value: props.user.weekStart,
|
||||
|
@ -189,6 +198,7 @@ function SettingsView(props: ComponentProps<typeof Settings> & { localeProp: str
|
|||
const enteredWeekStartDay = selectedWeekStartDay.value;
|
||||
const enteredHideBranding = hideBrandingRef.current.checked;
|
||||
const enteredLanguage = selectedLanguage.value;
|
||||
const enteredTimeFormat = selectedTimeFormat.value;
|
||||
|
||||
// TODO: Add validation
|
||||
|
||||
|
@ -204,6 +214,7 @@ function SettingsView(props: ComponentProps<typeof Settings> & { localeProp: str
|
|||
theme: asStringOrNull(selectedTheme?.value),
|
||||
brandColor: enteredBrandColor,
|
||||
locale: enteredLanguage,
|
||||
timeFormat: enteredTimeFormat,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -347,6 +358,21 @@ function SettingsView(props: ComponentProps<typeof Settings> & { localeProp: str
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="timeFormat" className="block text-sm font-medium text-gray-700">
|
||||
{t("time_format")}
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<Select
|
||||
id="timeFormatSelect"
|
||||
value={selectedTimeFormat || props.user.timeFormat}
|
||||
onChange={(v) => v && setSelectedTimeFormat(v)}
|
||||
classNamePrefix="react-select"
|
||||
className="react-select-container mt-1 block w-full rounded-sm border border-gray-300 capitalize shadow-sm focus:border-neutral-800 focus:ring-neutral-800 sm:text-sm"
|
||||
options={timeFormatOptions}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="weekStart" className="block text-sm font-medium text-gray-700">
|
||||
{t("first_day_of_week")}
|
||||
|
@ -499,6 +525,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
|||
plan: true,
|
||||
brandColor: true,
|
||||
metadata: true,
|
||||
timeFormat: true,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -659,5 +659,8 @@
|
|||
"contact_sales": "Contact Sales",
|
||||
"error_404": "Error 404",
|
||||
"requires_ownership_of_a_token": "Requires ownership of a token belonging to the following address:",
|
||||
"example_name": "John Doe"
|
||||
"example_name": "John Doe",
|
||||
"time_format": "Time format",
|
||||
"12_hour": "12 hour",
|
||||
"24_hour": "24 hour"
|
||||
}
|
||||
|
|
|
@ -66,6 +66,7 @@ async function getUserFromSession({
|
|||
completedOnboarding: true,
|
||||
destinationCalendar: true,
|
||||
locale: true,
|
||||
timeFormat: true,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -75,6 +75,7 @@ const loggedInViewerRouter = createProtectedRouter()
|
|||
endTime: user.endTime,
|
||||
bufferTime: user.bufferTime,
|
||||
locale: user.locale,
|
||||
timeFormat: user.timeFormat,
|
||||
avatar: user.avatar,
|
||||
createdDate: user.createdDate,
|
||||
completedOnboarding: user.completedOnboarding,
|
||||
|
@ -612,6 +613,7 @@ const loggedInViewerRouter = createProtectedRouter()
|
|||
theme: z.string().optional().nullable(),
|
||||
completedOnboarding: z.boolean().optional(),
|
||||
locale: z.string().optional(),
|
||||
timeFormat: z.number().optional(),
|
||||
}),
|
||||
async resolve({ input, ctx }) {
|
||||
const { user, prisma } = ctx;
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
-- AlterTable
|
||||
ALTER TABLE "users" ADD COLUMN "timeFormat" INTEGER DEFAULT 12;
|
|
@ -128,6 +128,7 @@ model User {
|
|||
selectedCalendars SelectedCalendar[]
|
||||
completedOnboarding Boolean @default(false)
|
||||
locale String?
|
||||
timeFormat Int? @default(12)
|
||||
twoFactorSecret String?
|
||||
twoFactorEnabled Boolean @default(false)
|
||||
identityProvider IdentityProvider @default(CAL)
|
||||
|
|
Loading…
Reference in a new issue