Let users set 12/24 hour time format (#2002)

This commit is contained in:
Bailey Pumfleet 2022-02-28 16:24:47 +00:00 committed by GitHub
parent 2559873b2c
commit 7826a34b00
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 53 additions and 5 deletions

View file

@ -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" : "")}>

View file

@ -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([])}

View file

@ -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,
},
});

View file

@ -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"
}

View file

@ -66,6 +66,7 @@ async function getUserFromSession({
completedOnboarding: true,
destinationCalendar: true,
locale: true,
timeFormat: true,
},
});

View file

@ -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;

View file

@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "users" ADD COLUMN "timeFormat" INTEGER DEFAULT 12;

View file

@ -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)