From 6220e599cfc758f9aca0b4ebfb3adf13befb67be Mon Sep 17 00:00:00 2001 From: Ben Grant Date: Wed, 24 May 2023 19:10:52 +1000 Subject: [PATCH] Add giraugh tools and update dayjs instance on client when lang changes --- frontend/package.json | 1 + .../CalendarField/components/Month/Month.tsx | 16 +++++----- .../components/Weekdays/Weekdays.tsx | 8 ++--- .../src/components/CreateForm/CreateForm.tsx | 3 +- frontend/src/components/Recents/Recents.tsx | 3 +- frontend/src/components/Settings/Settings.tsx | 19 +----------- frontend/src/config/dayjs.ts | 31 ++++++++++++++++++- frontend/src/i18n/client.ts | 3 +- frontend/src/i18n/server.ts | 3 +- frontend/yarn.lock | 5 +++ 10 files changed, 53 insertions(+), 39 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index 8f61d33..537397c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,6 +11,7 @@ }, "dependencies": { "@azure/msal-browser": "^2.37.0", + "@giraugh/tools": "^1.5.0", "@microsoft/microsoft-graph-client": "^3.0.5", "accept-language": "^3.0.18", "dayjs": "^1.11.7", diff --git a/frontend/src/components/CalendarField/components/Month/Month.tsx b/frontend/src/components/CalendarField/components/Month/Month.tsx index 4ac1147..1b25487 100644 --- a/frontend/src/components/CalendarField/components/Month/Month.tsx +++ b/frontend/src/components/CalendarField/components/Month/Month.tsx @@ -1,8 +1,10 @@ import { useCallback, useEffect, useRef, useState } from 'react' +import { rotateArray } from '@giraugh/tools' +import { Dayjs } from 'dayjs' import { ChevronLeft, ChevronRight } from 'lucide-react' import Button from '/src/components/Button/Button' -import dayjs from '/src/config/dayjs' +import { useDayjs } from '/src/config/dayjs' import { useTranslation } from '/src/i18n/client' import { useStore } from '/src/stores' import useSettingsStore from '/src/stores/settingsStore' @@ -10,10 +12,6 @@ import { makeClass } from '/src/utils' import styles from './Month.module.scss' -// TODO: use from giraugh tools -export const rotateArray = (arr: T[], amount = 1): T[] => - arr.map((_, i) => arr[((( -amount + i ) % arr.length) + arr.length) % arr.length]) - interface MonthProps { /** Array of dates in `DDMMYYYY` format */ value: string[] @@ -22,6 +20,7 @@ interface MonthProps { const Month = ({ value, onChange }: MonthProps) => { const { t } = useTranslation('home') + const dayjs = useDayjs() const weekStart = useStore(useSettingsStore, state => state.weekStart) ?? 0 @@ -29,7 +28,7 @@ const Month = ({ value, onChange }: MonthProps) => { month: dayjs().month(), year: dayjs().year(), }) - const [dates, setDates] = useState(calculateMonth(page, weekStart)) + const [dates, setDates] = useState(calculateMonth(dayjs().month(page.month).year(page.year), weekStart)) // Ref and state required to rerender but also access static version in callbacks const selectingRef = useRef([]) @@ -45,7 +44,7 @@ const Month = ({ value, onChange }: MonthProps) => { // Update month view useEffect(() => { dayjs.updateLocale(dayjs.locale(), { weekStart }) - setDates(calculateMonth(page, weekStart)) + setDates(calculateMonth(dayjs().month(page.month).year(page.year), weekStart)) }, [weekStart, page]) const handleFinishSelection = useCallback(() => { @@ -149,8 +148,7 @@ interface Date { } /** Calculate the dates to show for the month in a 2d array */ -const calculateMonth = ({ month, year }: { month: number, year: number }, weekStart: 0 | 1) => { - const date = dayjs().month(month).year(year) +const calculateMonth = (date: Dayjs, weekStart: 0 | 1) => { const daysInMonth = date.daysInMonth() const daysBefore = date.date(1).day() - weekStart const daysAfter = 6 - date.date(daysInMonth).day() + weekStart diff --git a/frontend/src/components/CalendarField/components/Weekdays/Weekdays.tsx b/frontend/src/components/CalendarField/components/Weekdays/Weekdays.tsx index eeba8a5..9d922ae 100644 --- a/frontend/src/components/CalendarField/components/Weekdays/Weekdays.tsx +++ b/frontend/src/components/CalendarField/components/Weekdays/Weekdays.tsx @@ -1,6 +1,7 @@ import { useCallback, useMemo, useRef, useState } from 'react' +import { rotateArray } from '@giraugh/tools' -import dayjs from '/src/config/dayjs' +import { useDayjs } from '/src/config/dayjs' import { useTranslation } from '/src/i18n/client' import { useStore } from '/src/stores' import useSettingsStore from '/src/stores/settingsStore' @@ -9,10 +10,6 @@ import { makeClass } from '/src/utils' // Use styles from Month picker import styles from '../Month/Month.module.scss' -// TODO: use from giraugh tools -export const rotateArray = (arr: T[], amount = 1): T[] => - arr.map((_, i) => arr[((( -amount + i ) % arr.length) + arr.length) % arr.length]) - interface WeekdaysProps { /** Array of weekdays as numbers from 0-6 (as strings) */ value: string[] @@ -21,6 +18,7 @@ interface WeekdaysProps { const Weekdays = ({ value, onChange }: WeekdaysProps) => { const { t } = useTranslation('home') + const dayjs = useDayjs() const weekStart = useStore(useSettingsStore, state => state.weekStart) ?? 0 diff --git a/frontend/src/components/CreateForm/CreateForm.tsx b/frontend/src/components/CreateForm/CreateForm.tsx index d141470..4c6ffe3 100644 --- a/frontend/src/components/CreateForm/CreateForm.tsx +++ b/frontend/src/components/CreateForm/CreateForm.tsx @@ -11,7 +11,7 @@ import SelectField from '/src/components/SelectField/SelectField' import TextField from '/src/components/TextField/TextField' import TimeRangeField from '/src/components/TimeRangeField/TimeRangeField' import { API_BASE } from '/src/config/api' -import dayjs from '/src/config/dayjs' +import { useDayjs } from '/src/config/dayjs' import { useTranslation } from '/src/i18n/client' import timezones from '/src/res/timezones.json' @@ -36,6 +36,7 @@ const defaultValues: Fields = { const CreateForm = () => { const { t } = useTranslation('home') + const dayjs = useDayjs() const { push } = useRouter() const { diff --git a/frontend/src/components/Recents/Recents.tsx b/frontend/src/components/Recents/Recents.tsx index 79a9d19..51397ed 100644 --- a/frontend/src/components/Recents/Recents.tsx +++ b/frontend/src/components/Recents/Recents.tsx @@ -4,7 +4,7 @@ import Link from 'next/link' import Content from '/src/components/Content/Content' import Section from '/src/components/Section/Section' -import dayjs from '/src/config/dayjs' +import { useDayjs } from '/src/config/dayjs' import { useTranslation } from '/src/i18n/client' import { useStore } from '/src/stores' import useRecentsStore from '/src/stores/recentsStore' @@ -18,6 +18,7 @@ interface RecentsProps { const Recents = ({ target }: RecentsProps) => { const recents = useStore(useRecentsStore, state => state.recents) const { t } = useTranslation(['home', 'common']) + const dayjs = useDayjs() return recents?.length ?
diff --git a/frontend/src/components/Settings/Settings.tsx b/frontend/src/components/Settings/Settings.tsx index 25f202a..9b9b46b 100644 --- a/frontend/src/components/Settings/Settings.tsx +++ b/frontend/src/components/Settings/Settings.tsx @@ -7,7 +7,6 @@ import { Settings as SettingsIcon } from 'lucide-react' import SelectField from '/src/components/SelectField/SelectField' import ToggleField from '/src/components/ToggleField/ToggleField' -import dayjs from '/src/config/dayjs' import { useTranslation } from '/src/i18n/client' import { languageDetails } from '/src/i18n/options' import { useStore } from '/src/stores' @@ -16,12 +15,6 @@ import { makeClass, unhyphenate } from '/src/utils' import styles from './Settings.module.scss' -// TODO: add to giraugh tools -const isKeyOfObject = ( - key: string | number | symbol, - obj: T, -): key is keyof T => key in obj - const Settings = () => { const { t, i18n } = useTranslation('common') const router = useRouter() @@ -128,17 +121,7 @@ const Settings = () => { }} isSmall value={i18n.language} - onChange={e => { - if (isKeyOfObject(e.target.value, languageDetails)) { - store?.setWeekStart(languageDetails[e.target.value].weekStart) - store?.setTimeFormat(languageDetails[e.target.value].timeFormat) - - languageDetails[e.target.value]?.import().then(() => { - dayjs.locale(e.target.value) - }) - } - i18n.changeLanguage(e.target.value).then(() => router.refresh()) - }} + onChange={e => i18n.changeLanguage(e.target.value).then(() => router.refresh())} /> diff --git a/frontend/src/config/dayjs.ts b/frontend/src/config/dayjs.ts index 2df448c..4e2729f 100644 --- a/frontend/src/config/dayjs.ts +++ b/frontend/src/config/dayjs.ts @@ -1,3 +1,5 @@ +import { useCallback, useState } from 'react' +import { isKeyOfObject } from '@giraugh/tools' import dayjs from 'dayjs' import customParseFormat from 'dayjs/plugin/customParseFormat' import isToday from 'dayjs/plugin/isToday' @@ -7,6 +9,11 @@ import timezone from 'dayjs/plugin/timezone' import updateLocale from 'dayjs/plugin/updateLocale' import utc from 'dayjs/plugin/utc' +import { useTranslation } from '/src/i18n/client' +import { languageDetails } from '/src/i18n/options' +import { useStore } from '/src/stores' +import useSettingsStore from '/src/stores/settingsStore' + dayjs.extend(customParseFormat) dayjs.extend(isToday) dayjs.extend(localeData) @@ -15,4 +22,26 @@ dayjs.extend(timezone) dayjs.extend(updateLocale) dayjs.extend(utc) -export default dayjs +export const useDayjs = () => { + const { i18n } = useTranslation() + const store = useStore(useSettingsStore, state => state) + const [updateInstance, setUpdateInstance] = useState(0) + + const instance = useCallback(dayjs, [updateInstance, dayjs]) + + const handleLanguageChange = useCallback((lng: string) => { + if (isKeyOfObject(lng, languageDetails)) { + store?.setWeekStart(languageDetails[lng].weekStart) + store?.setTimeFormat(languageDetails[lng].timeFormat) + + languageDetails[lng]?.import().then(() => { + dayjs.locale(lng) + setUpdateInstance(updateInstance + 1) + }) + } + }, [store]) + + i18n.on('languageChanged', handleLanguageChange) + + return instance +} diff --git a/frontend/src/i18n/client.ts b/frontend/src/i18n/client.ts index 1bd0946..f8cad7b 100644 --- a/frontend/src/i18n/client.ts +++ b/frontend/src/i18n/client.ts @@ -2,12 +2,11 @@ import { initReactI18next, useTranslation as useTranslationHook } from 'react-i18next' import { cookies } from 'next/dist/client/components/headers' // risky disky (undocumented???) +import dayjs from 'dayjs' import i18next from 'i18next' import LanguageDetector from 'i18next-browser-languagedetector' import resourcesToBackend from 'i18next-resources-to-backend' -import dayjs from '/src/config/dayjs' - import { cookieName, getOptions, languageDetails } from './options' i18next diff --git a/frontend/src/i18n/server.ts b/frontend/src/i18n/server.ts index d48a8c1..5704fce 100644 --- a/frontend/src/i18n/server.ts +++ b/frontend/src/i18n/server.ts @@ -1,10 +1,9 @@ import { cookies, headers } from 'next/headers' import acceptLanguage from 'accept-language' +import dayjs from 'dayjs' import { createInstance } from 'i18next' import resourcesToBackend from 'i18next-resources-to-backend' -import dayjs from '/src/config/dayjs' - import { cookieName, fallbackLng, getOptions, languageDetails, languages } from './options' type Mutable = { -readonly [K in keyof T]: Mutable } diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 478b114..894719d 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -65,6 +65,11 @@ resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.40.0.tgz#3ba73359e11f5a7bd3e407f70b3528abfae69cec" integrity sha512-ElyB54bJIhXQYVKjDSvCkPO1iU1tSAeVQJbllWJq1XQSmmA4dgFk8CbiBGpiOPxleE48vDogxCtmMYku4HSVLA== +"@giraugh/tools@^1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@giraugh/tools/-/tools-1.5.0.tgz#5c5cc8b248d1e04aebc46dcbeb7620c99f47d2ec" + integrity sha512-DZTrxKU5Ul5+UnDUNja0Cp1HcMLPGF2fh7j4ICEzaNwvRMwcHRH241L/pfneuibZ22w1Ka4U7LVzOWL+SjLIjw== + "@humanwhocodes/config-array@^0.11.8": version "0.11.8" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.8.tgz#03595ac2075a4dc0f191cc2131de14fbd7d410b9"