From c6338f8ba5fc3fcb96c3d4999a80e83355f4ebed Mon Sep 17 00:00:00 2001 From: MrQuackDuck Date: Fri, 3 Jan 2025 14:09:37 +0200 Subject: [PATCH] feat: add apperance settings with time format --- src/app/main.tsx | 5 +- src/assets/locale/de.json | 7 +- src/assets/locale/en.json | 7 +- src/assets/locale/es.json | 7 +- src/assets/locale/fr.json | 7 +- src/assets/locale/pl.json | 7 +- src/assets/locale/ru.json | 7 +- src/assets/locale/uk.json | 7 +- src/entities/Message/ui/Message.tsx | 19 ++---- src/pages/settings/ui/AppearanceSettings.tsx | 65 +++++++++++++++++++ src/pages/settings/ui/SettingsPage.tsx | 2 + src/pages/settings/ui/SettingsTabs.tsx | 5 +- src/shared/lib/hooks/index.ts | 1 + src/shared/lib/hooks/useFormatDate.ts | 37 +++++++++++ .../lib/providers/TimeFormatProvider.tsx | 38 +++++++++++ src/shared/lib/providers/index.ts | 1 + src/shared/model/SettingsTabs.ts | 1 + 17 files changed, 201 insertions(+), 22 deletions(-) create mode 100644 src/pages/settings/ui/AppearanceSettings.tsx create mode 100644 src/shared/lib/hooks/useFormatDate.ts create mode 100644 src/shared/lib/providers/TimeFormatProvider.tsx diff --git a/src/app/main.tsx b/src/app/main.tsx index 1562554..c65a23f 100644 --- a/src/app/main.tsx +++ b/src/app/main.tsx @@ -8,6 +8,7 @@ import { AuthProvider } from "@/features/authorize"; import { UsersVolumeProvider } from "@/features/control-user-volume"; import { SettingsOpenCloseProvider } from "@/features/open-close-settings"; import { EncryptionKeysProvider, LanguageSettingsProvider, LoadingProvider, NotificationsSettingsProvider, ThemeProvider, TranslationProvider, VoiceSettingsProvider } from "@/shared/lib"; +import { TimeFormatSettingsProvider } from "@/shared/lib/providers/TimeFormatProvider"; import { TooltipProvider } from "@/shared/ui"; import { ChatConnectionsProvider } from "@/widgets/chat-section"; @@ -30,7 +31,9 @@ createRoot(document.getElementById("root")!).render( - + + + diff --git a/src/assets/locale/de.json b/src/assets/locale/de.json index d7754ce..af0e37d 100644 --- a/src/assets/locale/de.json +++ b/src/assets/locale/de.json @@ -152,6 +152,7 @@ "COPY": "Kopieren", "DOWNLOAD": "Herunterladen", "ACCOUNT": "Konto", + "APPEARANCE": "Erscheinungsbild", "COLIR_ID_AKA_HEX_ID": "Colir ID (oder Hex-ID)", "IT_CANT_BE_CHANGED": "*Kann nicht geändert werden", "VOICE_SETTINGS": "Spracheinstellungen", @@ -229,5 +230,9 @@ "ESC_CLOSE": "[Esc] Schließen", "ROOM_OWNER": "Raumbesitzer", "ERROR_CODE": "Fehlercode", - "SENDING": "Senden..." + "SENDING": "Senden...", + "TIME_FORMAT": "Zeitformat", + "EXAMPLE": "Beispiel", + "12_HOUR": "12-Stunden", + "24_HOUR": "24-Stunden" } diff --git a/src/assets/locale/en.json b/src/assets/locale/en.json index 41fee96..29204ae 100644 --- a/src/assets/locale/en.json +++ b/src/assets/locale/en.json @@ -152,6 +152,7 @@ "COPY": "Copy", "DOWNLOAD": "Download", "ACCOUNT": "Account", + "APPEARANCE": "Appearance", "COLIR_ID_AKA_HEX_ID": "Colir Id (aka. Hex Id)", "IT_CANT_BE_CHANGED": "*It can't be changed", "VOICE_SETTINGS": "Voice Settings", @@ -229,5 +230,9 @@ "ESC_CLOSE": "[Esc] Close", "ROOM_OWNER": "Room owner", "ERROR_CODE": "Error code", - "SENDING": "Sending..." + "SENDING": "Sending...", + "TIME_FORMAT": "Time format", + "EXAMPLE": "Example", + "12_HOUR": "12-hour", + "24_HOUR": "24-hour" } diff --git a/src/assets/locale/es.json b/src/assets/locale/es.json index 9091ad0..43b4614 100644 --- a/src/assets/locale/es.json +++ b/src/assets/locale/es.json @@ -152,6 +152,7 @@ "COPY": "Copiar", "DOWNLOAD": "Descargar", "ACCOUNT": "Cuenta", + "APPEARANCE": "Apariencia", "COLIR_ID_AKA_HEX_ID": "Colir ID (o ID hexadecimal)", "IT_CANT_BE_CHANGED": "*No se puede cambiar", "VOICE_SETTINGS": "Configuración de voz", @@ -229,5 +230,9 @@ "ESC_CLOSE": "[Esc] Cerrar", "ROOM_OWNER": "Propietario de la sala", "ERROR_CODE": "Código de error", - "SENDING": "Enviando..." + "SENDING": "Enviando...", + "TIME_FORMAT": "Formato de hora", + "EXAMPLE": "Ejemplo", + "12_HOUR": "12 horas", + "24_HOUR": "24 horas" } diff --git a/src/assets/locale/fr.json b/src/assets/locale/fr.json index d236092..4a2fde9 100644 --- a/src/assets/locale/fr.json +++ b/src/assets/locale/fr.json @@ -152,6 +152,7 @@ "COPY": "Copier", "DOWNLOAD": "Télécharger", "ACCOUNT": "Compte", + "APPEARANCE": "Apparence", "COLIR_ID_AKA_HEX_ID": "Colir ID (ou ID hexadécimal)", "IT_CANT_BE_CHANGED": "*Ne peut pas être modifié", "VOICE_SETTINGS": "Paramètres vocaux", @@ -229,5 +230,9 @@ "ESC_CLOSE": "[Échap] Fermer", "ROOM_OWNER": "Propriétaire de la salle", "ERROR_CODE": "Code d'erreur", - "SENDING": "Envoi en cours..." + "SENDING": "Envoi en cours...", + "TIME_FORMAT": "Format de l'heure", + "EXAMPLE": "Exemple", + "12_HOUR": "12 heures", + "24_HOUR": "24 heures" } diff --git a/src/assets/locale/pl.json b/src/assets/locale/pl.json index f47e959..69d5b2c 100644 --- a/src/assets/locale/pl.json +++ b/src/assets/locale/pl.json @@ -152,6 +152,7 @@ "COPY": "Kopiuj", "DOWNLOAD": "Pobierz", "ACCOUNT": "Konto", + "APPEARANCE": "Wygląd", "COLIR_ID_AKA_HEX_ID": "Colir ID (lub hex ID)", "IT_CANT_BE_CHANGED": "*Nie można zmienić", "VOICE_SETTINGS": "Ustawienia głosu", @@ -229,5 +230,9 @@ "ESC_CLOSE": "[Esc] Zamknij", "ROOM_OWNER": "Właściciel pokoju", "ERROR_CODE": "Kod błędu", - "SENDING": "Wysyłanie..." + "SENDING": "Wysyłanie...", + "TIME_FORMAT": "Format czasu", + "EXAMPLE": "Przykład", + "12_HOUR": "12-godzinny", + "24_HOUR": "24-godzinny" } diff --git a/src/assets/locale/ru.json b/src/assets/locale/ru.json index 2353496..29d8a3f 100644 --- a/src/assets/locale/ru.json +++ b/src/assets/locale/ru.json @@ -152,6 +152,7 @@ "COPY": "Копировать", "DOWNLOAD": "Скачать", "ACCOUNT": "Аккаунт", + "APPEARANCE": "Внешний вид", "COLIR_ID_AKA_HEX_ID": "Colir ID (или hex ID)", "IT_CANT_BE_CHANGED": "*Это нельзя поменять", "VOICE_SETTINGS": "Настройки голоса", @@ -229,5 +230,9 @@ "ESC_CLOSE": "[Esc] Закрыть", "ROOM_OWNER": "Владелец комнаты", "ERROR_CODE": "Код ошибки", - "SENDING": "Отправка..." + "SENDING": "Отправка...", + "TIME_FORMAT": "Формат времени", + "EXAMPLE": "Пример", + "12_HOUR": "12-часовой", + "24_HOUR": "24-часовой" } diff --git a/src/assets/locale/uk.json b/src/assets/locale/uk.json index 4687d1e..f6e8966 100644 --- a/src/assets/locale/uk.json +++ b/src/assets/locale/uk.json @@ -152,6 +152,7 @@ "COPY": "Копіювати", "DOWNLOAD": "Завантажити", "ACCOUNT": "Обліковий запис", + "APPEARANCE": "Зовнішній вигляд", "COLIR_ID_AKA_HEX_ID": "Colir ID (або hex ID)", "IT_CANT_BE_CHANGED": "*Не можна змінити", "VOICE_SETTINGS": "Налаштування голосу", @@ -229,5 +230,9 @@ "ESC_CLOSE": "[Esc] Закрити", "ROOM_OWNER": "Власник кімнати", "ERROR_CODE": "Код помилки", - "SENDING": "Надсилання..." + "SENDING": "Надсилання...", + "TIME_FORMAT": "Формат часу", + "EXAMPLE": "Приклад", + "12_HOUR": "12-годинний", + "24_HOUR": "24-годинний" } diff --git a/src/entities/Message/ui/Message.tsx b/src/entities/Message/ui/Message.tsx index 6c41428..4bb2730 100644 --- a/src/entities/Message/ui/Message.tsx +++ b/src/entities/Message/ui/Message.tsx @@ -8,7 +8,7 @@ import { useContextSelector } from "use-context-selector"; import { AttachmentsSection } from "@/entities/Attachment"; import { ReactionBar } from "@/entities/Reaction"; import { CurrentUserContext, UserModel, Username } from "@/entities/User"; -import { cn, decryptString, encryptString, LanguageSettingsContext, replaceEmojis, useInfoToast, useTheme, useTranslation } from "@/shared/lib"; +import { cn, decryptString, encryptString, LanguageSettingsContext, replaceEmojis, useFormatDate, useInfoToast, useTheme, useTranslation } from "@/shared/lib"; import { Button, ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuTrigger, EmojiPicker, Separator, Tooltip, TooltipContent, TooltipTrigger } from "@/shared/ui"; import { formatText } from "../lib/formatText"; @@ -204,14 +204,7 @@ const Message = forwardRef( setShiftPressed(event.shiftKey); } - function formatDate(date) { - const now = Moment(); - const givenDate = Moment(date); - - if (givenDate.isSame(now, "day")) return givenDate.format("h:mm A"); - if (givenDate.isSame(now.add(1, "day"), "day")) return givenDate.format("MMMM D, h:mm A"); - return givenDate.format("MMMM D, h:mm A"); - } + const { formatDateShortened, formatFullDate } = useFormatDate(); // Preventing context menu on the attachments and the reaction bar (because it's already handled by the ReactionBar component) function validateContextMenu(event: React.MouseEvent) { @@ -274,9 +267,9 @@ const Message = forwardRef(
- {{formatDate(message.postDate)}} + {{formatDateShortened(message.postDate)}} - {Moment(message.postDate).format("LLLL")} + {formatFullDate(message.postDate)} {message.editDate && ( @@ -286,12 +279,12 @@ const Message = forwardRef( { - {t("EDITED")} {formatDate(message.editDate)} + {t("EDITED")} {formatDateShortened(message.editDate)} } - {Moment(message.editDate).format("LLLL")} + {formatFullDate(message.editDate)} diff --git a/src/pages/settings/ui/AppearanceSettings.tsx b/src/pages/settings/ui/AppearanceSettings.tsx new file mode 100644 index 0000000..d4f6773 --- /dev/null +++ b/src/pages/settings/ui/AppearanceSettings.tsx @@ -0,0 +1,65 @@ +import { useContextSelector } from "use-context-selector"; + +import { useFormatDate, useTheme, useTranslation } from "@/shared/lib"; +import { TimeFormatSettingsContext } from "@/shared/lib/providers/TimeFormatProvider"; +import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue, Separator } from "@/shared/ui"; + +function AppearanceSettings() { + const t = useTranslation(); + const { theme, setTheme } = useTheme(); + const { timeFormat, setTimeFormat } = useContextSelector(TimeFormatSettingsContext, (c) => c); + const { formatFullDate } = useFormatDate(); + const exampleTime: Date = new Date(2025, 11, 19, 13, 27); + + function handleThemeChange(value: string) { + if (value == "dark" || value == "light") setTheme(value); + } + + function handleTimeFormatChange(value: string) { + if (value == "12-hour" || value == "24-hour") setTimeFormat(value); + } + + return ( +
+ {t("APPEARANCE")} + +
+
+ {t("THEME")} + +
+
+ {t("TIME_FORMAT")} + + + {t("EXAMPLE")}: {formatFullDate(exampleTime)} + +
+
+
+ ); +} + +export default AppearanceSettings; diff --git a/src/pages/settings/ui/SettingsPage.tsx b/src/pages/settings/ui/SettingsPage.tsx index 8481361..af37877 100644 --- a/src/pages/settings/ui/SettingsPage.tsx +++ b/src/pages/settings/ui/SettingsPage.tsx @@ -9,6 +9,7 @@ import { SettingsTabs as SettingsTabsEnum } from "@/shared/model"; import { Button, PopupWindow, ScrollArea, Separator, Sheet, SheetContent, SheetDescription, SheetTitle } from "@/shared/ui"; import AccountSettings from "./AccountSettings"; +import AppearanceSettings from "./AppearanceSettings"; import ImportExportSettings from "./ImportExportSettings"; import LanguageSettings from "./LanguageSettings"; import NotificationsSettings from "./NotificationsSettings"; @@ -76,6 +77,7 @@ export function SettingsPage() {
{selectedTab == SettingsTabsEnum.Account && } + {selectedTab == SettingsTabsEnum.Appearance && } {selectedTab == SettingsTabsEnum.VoiceSettings && } {selectedTab == SettingsTabsEnum.Notifications && } {selectedTab == SettingsTabsEnum.Statistics && } diff --git a/src/pages/settings/ui/SettingsTabs.tsx b/src/pages/settings/ui/SettingsTabs.tsx index 179b681..834a5d8 100644 --- a/src/pages/settings/ui/SettingsTabs.tsx +++ b/src/pages/settings/ui/SettingsTabs.tsx @@ -1,4 +1,4 @@ -import { BarChart3Icon, GlobeIcon, ImportIcon, MegaphoneIcon, UserIcon, Volume2Icon } from "lucide-react"; +import { BarChart3Icon, GlobeIcon, ImportIcon, MegaphoneIcon, Sparkles, UserIcon, Volume2Icon } from "lucide-react"; import { useTranslation } from "@/shared/lib"; import { SettingsTabs as SettingsTabsEnum } from "@/shared/model"; @@ -20,6 +20,9 @@ function SettingsTabs({ className, selectedTab, setSelectedTab }: SettingsTabsPr setSelectedTab(SettingsTabsEnum.Account)}> {t("ACCOUNT")} + setSelectedTab(SettingsTabsEnum.Appearance)}> + {t("APPEARANCE")} + setSelectedTab(SettingsTabsEnum.VoiceSettings)}> {t("VOICE_SETTINGS")} diff --git a/src/shared/lib/hooks/index.ts b/src/shared/lib/hooks/index.ts index 76b12e1..06f29ac 100644 --- a/src/shared/lib/hooks/index.ts +++ b/src/shared/lib/hooks/index.ts @@ -1,5 +1,6 @@ export { useAdaptiveColor } from "./useAdaptiveColor"; export { useErrorToast } from "./useErrorToast"; +export { useFormatDate } from "./useFormatDate"; export { useImportExportSettings } from "./useImportExportSettings"; export { useInfoToast } from "./useInfoToast"; export { useInvertedScrollArea } from "./useInvertedScrollArea"; diff --git a/src/shared/lib/hooks/useFormatDate.ts b/src/shared/lib/hooks/useFormatDate.ts new file mode 100644 index 0000000..affd70e --- /dev/null +++ b/src/shared/lib/hooks/useFormatDate.ts @@ -0,0 +1,37 @@ +import "moment/min/locales"; + +import Moment from "moment/min/moment-with-locales"; +import { useContextSelector } from "use-context-selector"; + +import { TimeFormatSettingsContext } from "../providers/TimeFormatProvider"; + +export const useFormatDate = (): { + formatDateShortened: (date: Date) => string; + formatFullDate: (date: Date) => string; +} => { + const { timeFormat } = useContextSelector(TimeFormatSettingsContext, (c) => c); + + const formatDateShortened = (date: Date): string => { + const now = Moment(); + const givenDate = Moment(date); + const format = timeFormat === "12-hour" ? "h:mm A" : "HH:mm"; + + if (givenDate.isSame(now, "day")) { + return givenDate.format(format); + } + + if (givenDate.isSame(now.clone().add(1, "day"), "day")) { + return givenDate.format(`MMM D, ${format}`); + } + + return givenDate.format(`MMM D, ${format}`); + }; + + const formatFullDate = (date: Date): string => { + const givenDate = Moment(date); + const format = timeFormat === "12-hour" ? "h:mm A" : "HH:mm"; + return givenDate.format(`dddd, MMMM D, YYYY ${format}`); + }; + + return { formatDateShortened, formatFullDate }; +}; \ No newline at end of file diff --git a/src/shared/lib/providers/TimeFormatProvider.tsx b/src/shared/lib/providers/TimeFormatProvider.tsx new file mode 100644 index 0000000..59d05d6 --- /dev/null +++ b/src/shared/lib/providers/TimeFormatProvider.tsx @@ -0,0 +1,38 @@ +import { useEffect, useState } from "react"; +import { createContext } from "use-context-selector"; + +import { useLocalStorage } from "../hooks/useLocalStorage"; + +type TimeFormat = "12-hour" | "24-hour"; + +export const TimeFormatSettingsContext = createContext<{ + timeFormat: TimeFormat; + setTimeFormat: (format: TimeFormat) => void; +}>({ + timeFormat: "12-hour", + setTimeFormat: () => {} +}); + +export const TimeFormatSettingsProvider = ({ children }) => { + const { getFromLocalStorage, setToLocalStorage } = useLocalStorage(); + const [timeFormat, setTimeFormat] = useState(getFromLocalStorage("timeFormat") ?? "12-hour"); + + function saveAllToLocalStorage() { + setToLocalStorage("timeFormat", timeFormat); + } + + useEffect(() => { + saveAllToLocalStorage(); + }, [timeFormat]); + + return ( + + {children} + + ); +}; diff --git a/src/shared/lib/providers/index.ts b/src/shared/lib/providers/index.ts index 433617a..5a89d3b 100644 --- a/src/shared/lib/providers/index.ts +++ b/src/shared/lib/providers/index.ts @@ -3,5 +3,6 @@ export { LanguageSettingsContext, LanguageSettingsProvider } from "./LanguageSet export { LoadingContext, LoadingProvider } from "./LoadingProvider"; export { NotificationsSettingsContext, NotificationsSettingsProvider } from "./NotificationsSettingsProvider"; export { ThemeProvider, useTheme } from "./ThemeProvider"; +export { TimeFormatSettingsContext, TimeFormatSettingsProvider } from "./TimeFormatProvider"; export { TranslationContext, TranslationProvider } from "./TranslationProvider"; export { VoiceSettingsContext, VoiceSettingsProvider } from "./VoiceSettingsProvider"; diff --git a/src/shared/model/SettingsTabs.ts b/src/shared/model/SettingsTabs.ts index d82b16b..590aa5c 100644 --- a/src/shared/model/SettingsTabs.ts +++ b/src/shared/model/SettingsTabs.ts @@ -1,5 +1,6 @@ export enum SettingsTabs { Account = "account", + Appearance = "appearance", VoiceSettings = "voice-settings", Notifications = "notifications", Statistics = "statistics",