From 63554f4196e587aae2d02e3e6e6302f32d3332c4 Mon Sep 17 00:00:00 2001 From: Casper Iversen <53900565+casperiv0@users.noreply.github.com> Date: Sun, 10 Sep 2023 13:07:55 +0200 Subject: [PATCH] feat: CAD Timezone (#1789) --- .../migration.sql | 2 + apps/api/prisma/schema.prisma | 1 + .../admin/manage/cad-settings/CadSettings.ts | 1 + apps/api/src/middlewares/auth/is-auth.ts | 1 + apps/client/locales/en/cad-settings.json | 4 +- .../general/general-settings-tab.tsx | 31 +- .../cad-settings/general/timezones.json | 423 ++++++++++++++++++ apps/client/src/hooks/shared/useTime.ts | 16 +- apps/client/src/pages/_app.tsx | 3 + packages/schemas/src/admin/index.ts | 1 + packages/types/src/index.ts | 3 +- packages/ui/src/components/full-date.tsx | 7 +- 12 files changed, 484 insertions(+), 9 deletions(-) create mode 100644 apps/api/prisma/migrations/20230910082136_cad_time_zone/migration.sql create mode 100644 apps/client/src/components/admin/manage/cad-settings/general/timezones.json diff --git a/apps/api/prisma/migrations/20230910082136_cad_time_zone/migration.sql b/apps/api/prisma/migrations/20230910082136_cad_time_zone/migration.sql new file mode 100644 index 000000000..e14758c16 --- /dev/null +++ b/apps/api/prisma/migrations/20230910082136_cad_time_zone/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "cad" ADD COLUMN "timeZone" TEXT; diff --git a/apps/api/prisma/schema.prisma b/apps/api/prisma/schema.prisma index da2038587..51a0841d0 100644 --- a/apps/api/prisma/schema.prisma +++ b/apps/api/prisma/schema.prisma @@ -40,6 +40,7 @@ model cad { autoSetUserPropertiesId String? discordRoles DiscordRoles? @relation(fields: [discordRolesId], references: [id]) discordRolesId String? + timeZone String? } model CadFeature { diff --git a/apps/api/src/controllers/admin/manage/cad-settings/CadSettings.ts b/apps/api/src/controllers/admin/manage/cad-settings/CadSettings.ts index defa6823b..b959a1495 100644 --- a/apps/api/src/controllers/admin/manage/cad-settings/CadSettings.ts +++ b/apps/api/src/controllers/admin/manage/cad-settings/CadSettings.ts @@ -149,6 +149,7 @@ export class CADSettingsController { businessWhitelisted: data.businessWhitelisted, registrationCode: data.registrationCode, logoId: data.image, + timeZone: data.timeZone || null, miscCadSettings: { update: { roleplayEnabled: data.roleplayEnabled } }, }, include: { features: true, miscCadSettings: true, apiToken: true }, diff --git a/apps/api/src/middlewares/auth/is-auth.ts b/apps/api/src/middlewares/auth/is-auth.ts index e9af52048..ce497975b 100644 --- a/apps/api/src/middlewares/auth/is-auth.ts +++ b/apps/api/src/middlewares/auth/is-auth.ts @@ -128,6 +128,7 @@ export function CAD_SELECT(options: CadSelectOptions) { businessWhitelisted: true, features: true, autoSetUserProperties: true, + timeZone: true, registrationCode: options.selectCADsettings, steamApiKey: options.selectCADsettings, apiTokenId: options.selectCADsettings, diff --git a/apps/client/locales/en/cad-settings.json b/apps/client/locales/en/cad-settings.json index 005b60ce5..ac227fce3 100644 --- a/apps/client/locales/en/cad-settings.json +++ b/apps/client/locales/en/cad-settings.json @@ -23,7 +23,9 @@ "taxiWhitelist": "Taxi Whitelist", "taxiWhitelistDescription": "The taxi system will be whitelisted. The Taxi permission must be given to the user before they can use the taxi system.", "businessWhitelist": "Business Whitelist", - "businessWhitelistDescription": "The business system will be whitelisted. Any new business must first be reviewed, then they can be approved or denied before it can be used." + "businessWhitelistDescription": "The business system will be whitelisted. Any new business must first be reviewed, then they can be approved or denied before it can be used.", + "timeZone": "Time Zone", + "timeZoneDescription": "The time zone that will be used in the SnailyCAD instance. This will apply to all users." }, "DiscordRolesTab": { "discordRoles": "Discord Roles", diff --git a/apps/client/src/components/admin/manage/cad-settings/general/general-settings-tab.tsx b/apps/client/src/components/admin/manage/cad-settings/general/general-settings-tab.tsx index 5b2f2f438..e743cda10 100644 --- a/apps/client/src/components/admin/manage/cad-settings/general/general-settings-tab.tsx +++ b/apps/client/src/components/admin/manage/cad-settings/general/general-settings-tab.tsx @@ -2,7 +2,15 @@ import * as React from "react"; import { useAuth } from "context/AuthContext"; import { useTranslations } from "use-intl"; import useFetch from "lib/useFetch"; -import { Button, Input, Loader, SwitchField, TextField, Textarea } from "@snailycad/ui"; +import { + Button, + Input, + Loader, + SelectField, + SwitchField, + TextField, + Textarea, +} from "@snailycad/ui"; import { handleValidate } from "lib/handleValidate"; import { CAD_SETTINGS_SCHEMA } from "@snailycad/schemas"; import { ImageSelectInput, validateFile } from "components/form/inputs/ImageSelectInput"; @@ -14,6 +22,8 @@ import type { PutCADSettingsData } from "@snailycad/types/api"; import { TabsContent } from "@radix-ui/react-tabs"; import { SettingsTabs } from "components/admin/cad-settings/layout"; +import timeZones from "./timezones.json"; + export function GeneralSettingsTab() { const [logo, setLogo] = React.useState<(File | string) | null>(null); const [headerId, setHeaderId] = React.useState<(File | string) | null>(null); @@ -111,6 +121,7 @@ export function GeneralSettingsTab() { registrationCode: cad.registrationCode ?? "", roleplayEnabled: cad.miscCadSettings?.roleplayEnabled ?? true, cadOGDescription: cad.miscCadSettings?.cadOGDescription ?? "", + timeZone: cad?.timeZone ?? null, }; return ( @@ -146,6 +157,24 @@ export function GeneralSettingsTab() { /> + + ({ + label: tz, + value: tz, + }))} + selectedKey={values.timeZone} + onSelectionChange={(value) => setFieldValue("timeZone", value)} + /> + + {AOP ? ( (null); const isMounted = useMounted(); + const { dateTime } = useFormatter(); + const timeZone = useTimeZone(); React.useEffect(() => { function setTime() { if (ref.current && isMounted) { - ref.current.textContent = format(new Date(), "HH:mm:ss - yyyy-MM-dd"); + const time = dateTime(Date.now(), { + timeZone, + dateStyle: "medium", + timeStyle: "medium", + }); + + ref.current.textContent = `${time} ${timeZone ? `(${timeZone})` : ""}`; } } setTime(); - const interval = setInterval(setTime); + const interval = setInterval(setTime, 1_000); return () => { clearInterval(interval); }; - }, [isMounted]); + }, [isMounted, timeZone]); // eslint-disable-line react-hooks/exhaustive-deps return ref; } diff --git a/apps/client/src/pages/_app.tsx b/apps/client/src/pages/_app.tsx index 8a341031f..b89bd4c32 100644 --- a/apps/client/src/pages/_app.tsx +++ b/apps/client/src/pages/_app.tsx @@ -36,6 +36,8 @@ export default function App({ Component, router, pageProps, ...rest }: AppProps) const url = `${protocol}//${host}`; const user = pageProps.session as User | null; const locale = user?.locale ?? router.locale ?? "en"; + const cad = pageProps.cad ?? pageProps.session?.cad ?? null; + const timeZone = cad?.timeZone ?? undefined; React.useEffect(() => { const handleRouteStart = async () => { @@ -82,6 +84,7 @@ export default function App({ Component, router, pageProps, ...rest }: AppProps) defaultTranslationValues={{ span: (children) => {children}, }} + timeZone={timeZone} onError={console.warn} locale={locale} messages={pageProps.messages} diff --git a/packages/schemas/src/admin/index.ts b/packages/schemas/src/admin/index.ts index 743088c99..8126d0f44 100644 --- a/packages/schemas/src/admin/index.ts +++ b/packages/schemas/src/admin/index.ts @@ -11,6 +11,7 @@ export const CAD_SETTINGS_SCHEMA = z.object({ registrationCode: z.string().max(255).optional(), businessWhitelisted: z.boolean().optional(), image: z.any().nullish(), + timeZone: z.string().nullish(), }); export const LIVE_MAP_SETTINGS = z.object({ diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 53e9a3d73..44f7488d0 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -26,7 +26,8 @@ type CADPick = | "discordRoles" | "discordRolesId" | "version" - | "autoSetUserPropertiesId"; + | "autoSetUserPropertiesId" + | "timeZone"; export type cad = Pick< Omit & { diff --git a/packages/ui/src/components/full-date.tsx b/packages/ui/src/components/full-date.tsx index 6b2f4f578..680c301f1 100644 --- a/packages/ui/src/components/full-date.tsx +++ b/packages/ui/src/components/full-date.tsx @@ -1,5 +1,5 @@ import { HoverCard, HoverCardContent, HoverCardTrigger } from "../components/hover-card"; -import { useFormatter } from "use-intl"; +import { useFormatter, useTimeZone } from "use-intl"; interface FullDateProps { children: Date | string | number; @@ -10,6 +10,7 @@ interface FullDateProps { export function FullDate({ children, onlyDate, relative, isDateOfBirth }: FullDateProps) { const { dateTime, relativeTime } = useFormatter(); + const timezone = useTimeZone(); const isCorrectDate = isValidDate(children); if (!isCorrectDate) { @@ -23,6 +24,7 @@ export function FullDate({ children, onlyDate, relative, isDateOfBirth }: FullDa const relativeFormattedTime = relativeTime(date, new Date()); const formattedTime = dateTime(date, { + timeZone: timezone, dateStyle: "medium", timeStyle: onlyDate ? undefined : "medium", }); @@ -38,7 +40,8 @@ export function FullDate({ children, onlyDate, relative, isDateOfBirth }: FullDa - {dateTime(date, { dateStyle: "full", timeStyle: onlyDate ? undefined : "medium" })} + {dateTime(date, { dateStyle: "full", timeStyle: onlyDate ? undefined : "medium" })}{" "} + {timezone ? `(${timezone})` : ""}