From fb4d4efe662158eef96fc810812b4d64e97ce12e Mon Sep 17 00:00:00 2001 From: Mo Tarbin Date: Tue, 11 Aug 2026 17:42:23 -0400 Subject: [PATCH 1/2] Add report bug functionality and settings sections refactor --- public/locales/en/common.json | 3 +- src/constants/settingsSections.js | 34 +++++++ src/search/GlobalSearchContext.jsx | 5 +- src/search/searchProviders.js | 32 ++----- src/views/Settings/SettingsOverview.jsx | 121 +++--------------------- src/views/components/AddTaskModal.jsx | 79 +++++++++++++++- src/views/components/NavBar.jsx | 18 ++++ 7 files changed, 155 insertions(+), 137 deletions(-) create mode 100644 src/constants/settingsSections.js diff --git a/public/locales/en/common.json b/public/locales/en/common.json index dce97a7..f3962ff 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -29,7 +29,8 @@ "filters": "Filters", "activities": "Activities", "points": "Points", - "settings": "Settings" + "settings": "Settings", + "reportBug": "Report a Bug" }, "feedback": { "later": "Maybe later", diff --git a/src/constants/settingsSections.js b/src/constants/settingsSections.js new file mode 100644 index 0000000..3d64d8c --- /dev/null +++ b/src/constants/settingsSections.js @@ -0,0 +1,34 @@ +import { + AccountCircle, + Api, + Circle, + Code, + FamilyRestroom, + Language, + Notifications, + Palette, + Person, + Security, + Settings, + Storage, + ViewSidebar, +} from '@mui/icons-material' + +// Single source of truth for the settings sections: id, icon, and access +// gating. Titles/descriptions live in locales/settings.json under +// `overview.sections.`, keyed off the same ids. +export const SETTINGS_SECTIONS = [ + { id: 'profile', icon: Person }, + { id: 'circle', icon: Circle, parentOnly: true }, + { id: 'account', icon: AccountCircle, parentOnly: true }, + { id: 'subaccounts', icon: FamilyRestroom }, + { id: 'notifications', icon: Notifications }, + { id: 'mfa', icon: Security, parentOnly: true }, + { id: 'apitokens', icon: Api, parentOnly: true }, + { id: 'storage', icon: Storage }, + { id: 'sidepanel', icon: ViewSidebar }, + { id: 'theme', icon: Palette }, + { id: 'localization', icon: Language, isBeta: true }, + { id: 'advanced', icon: Settings }, + { id: 'developer', icon: Code }, +] diff --git a/src/search/GlobalSearchContext.jsx b/src/search/GlobalSearchContext.jsx index 3ebae49..eabc6ed 100644 --- a/src/search/GlobalSearchContext.jsx +++ b/src/search/GlobalSearchContext.jsx @@ -8,6 +8,7 @@ import { useMemo, useState, } from 'react' +import { useTranslation } from 'react-i18next' import { useLocation, useNavigate } from 'react-router-dom' import { offlineDB } from '../utils/OfflineDB' @@ -34,6 +35,7 @@ const uniqueBy = (items, getId) => [ export const GlobalSearchProvider = ({ children }) => { const queryClient = useQueryClient() + const { t } = useTranslation('settings') const location = useLocation() const navigate = useNavigate() const isMobile = useMediaQuery('(max-width:768px)') @@ -110,6 +112,7 @@ export const GlobalSearchProvider = ({ children }) => { labels, members, isParent: isParentUser(profile), + t, choresById: new Map(chores.map(item => [String(item.id), item])), projectsById: new Map(projects.map(item => [String(item.id), item])), membersById: new Map(members.map(item => [String(item.userId), item])), @@ -127,7 +130,7 @@ export const GlobalSearchProvider = ({ children }) => { } finally { setIsLoading(false) } - }, [queryClient]) + }, [queryClient, t]) const openSearch = useCallback( (query = '') => { diff --git a/src/search/searchProviders.js b/src/search/searchProviders.js index 7b61a54..13f5288 100644 --- a/src/search/searchProviders.js +++ b/src/search/searchProviders.js @@ -1,3 +1,5 @@ +import { SETTINGS_SECTIONS } from '../constants/settingsSections' + const stripHtml = value => { if (!value) return '' if (typeof globalThis.document === 'undefined') @@ -17,26 +19,6 @@ const HISTORY_STATUS = { 6: 'rescheduled', } -const SETTINGS = [ - ['profile', 'Profile', 'Name, avatar and personal details'], - ['circle', 'Circle', 'Members and household settings', true], - ['account', 'Account', 'Subscription and account management', true], - ['subaccounts', 'Subaccounts', 'Manage child accounts'], - ['notifications', 'Notifications', 'Reminders and notification preferences'], - ['mfa', 'Multi-factor authentication', 'Secure your account', true], - ['apitokens', 'API tokens', 'Manage integrations and access tokens', true], - ['storage', 'Storage', 'Files, backups and device storage'], - ['sidepanel', 'Side panel', 'Customize navigation'], - ['theme', 'Appearance', 'Theme, dark mode and colors'], - ['localization', 'Language and region', 'Language, dates and time formats'], - [ - 'advanced', - 'Advanced settings', - 'Offline support, webhooks and application behavior', - ], - ['developer', 'Developer settings', 'Diagnostics and experimental tools'], -] - const providers = [] export const registerSearchProvider = provider => { @@ -166,15 +148,15 @@ registerSearchProvider({ registerSearchProvider({ id: 'settings', - getDocuments: ({ isParent }) => - SETTINGS.filter(([, , , parentOnly]) => !parentOnly || isParent).map( - ([id, title, description]) => + getDocuments: ({ isParent, t }) => + SETTINGS_SECTIONS.filter(({ parentOnly }) => !parentOnly || isParent).map( + ({ id }) => document('settings', { id: `setting:${id}`, entityId: id, - title, + title: t(`overview.sections.${id}.title`), subtitle: 'Settings', - body: description, + body: t(`overview.sections.${id}.description`), keywords: `preferences configuration ${id}`, route: `/settings/${id}`, }), diff --git a/src/views/Settings/SettingsOverview.jsx b/src/views/Settings/SettingsOverview.jsx index 378e919..c3abe7c 100644 --- a/src/views/Settings/SettingsOverview.jsx +++ b/src/views/Settings/SettingsOverview.jsx @@ -1,22 +1,4 @@ -import { - AccountCircle, - Api, - BugReport, - ChevronRight, - Circle, - Code, - FamilyRestroom, - Feedback, - Language, - Notifications, - Palette, - Person, - Security, - Settings, - Star, - Storage, - ViewSidebar, -} from '@mui/icons-material' +import { BugReport, ChevronRight, Feedback, Star } from '@mui/icons-material' import { Avatar, Box, @@ -37,6 +19,7 @@ import { useState } from 'react' import { useTranslation } from 'react-i18next' import { useNavigate } from 'react-router-dom' +import { SETTINGS_SECTIONS } from '../../constants/settingsSections' import { useUserProfile } from '../../queries/UserQueries' import { isPlusAccount } from '../../utils/Helpers' import { isParentUser } from '../../utils/UserHelpers' @@ -51,85 +34,13 @@ const SettingsOverview = () => { const [bugReportOpen, setBugReportOpen] = useState(false) const settingsCards = [ - { - id: 'profile', - title: t('overview.sections.profile.title'), - description: t('overview.sections.profile.description'), - icon: , - }, - { - id: 'circle', - title: t('overview.sections.circle.title'), - description: t('overview.sections.circle.description'), - icon: , - }, - { - id: 'account', - title: t('overview.sections.account.title'), - description: t('overview.sections.account.description'), - icon: , - }, - { - id: 'subaccounts', - title: t('overview.sections.subaccounts.title'), - description: t('overview.sections.subaccounts.description'), - icon: , - }, - { - id: 'notifications', - title: t('overview.sections.notifications.title'), - description: t('overview.sections.notifications.description'), - icon: , - }, - { - id: 'mfa', - title: t('overview.sections.mfa.title'), - description: t('overview.sections.mfa.description'), - icon: , - }, - { - id: 'apitokens', - title: t('overview.sections.apitokens.title'), - description: t('overview.sections.apitokens.description'), - icon: , - }, - { - id: 'storage', - title: t('overview.sections.storage.title'), - description: t('overview.sections.storage.description'), - icon: , - }, - { - id: 'sidepanel', - title: t('overview.sections.sidepanel.title'), - description: t('overview.sections.sidepanel.description'), - icon: , - }, - { - id: 'theme', - title: t('overview.sections.theme.title'), - description: t('overview.sections.theme.description'), - icon: , - }, - { - id: 'localization', - title: t('overview.sections.localization.title'), - description: t('overview.sections.localization.description'), - icon: , - isBeta: true, - }, - { - id: 'advanced', - title: t('overview.sections.advanced.title'), - description: t('overview.sections.advanced.description'), - icon: , - }, - { - id: 'developer', - title: t('overview.sections.developer.title'), - description: t('overview.sections.developer.description'), - icon: , - }, + ...SETTINGS_SECTIONS.map(({ icon: Icon, id, isBeta }) => ({ + id, + title: t(`overview.sections.${id}.title`), + description: t(`overview.sections.${id}.description`), + icon: , + isBeta, + })), { id: 'feedback', title: t('overview.sections.feedback.title'), @@ -154,23 +65,19 @@ const SettingsOverview = () => { navigate(`/settings/${setting.id}`) } + const parentOnlyIds = SETTINGS_SECTIONS.filter( + section => section.parentOnly, + ).map(section => section.id) + // Filter settings based on user type const getAvailableSettings = () => { - const parentOnlySettings = [ - 'children', - 'mfa', - 'apitokens', - 'circle', - 'account', - ] - if (isParentUser(userProfile)) { // Parent users can access all settings return settingsCards } else { // Child users can only access basic settings return settingsCards.filter( - setting => !parentOnlySettings.includes(setting.id), + setting => !parentOnlyIds.includes(setting.id), ) } } diff --git a/src/views/components/AddTaskModal.jsx b/src/views/components/AddTaskModal.jsx index 255fb7d..27074fe 100644 --- a/src/views/components/AddTaskModal.jsx +++ b/src/views/components/AddTaskModal.jsx @@ -1,5 +1,14 @@ -import { Add } from '@mui/icons-material' -import { Box, Button, Typography } from '@mui/joy' +import { Add, KeyboardArrowDown } from '@mui/icons-material' +import { + Box, + Button, + Dropdown, + ListItemDecorator, + Menu, + MenuButton, + MenuItem, + Typography, +} from '@mui/joy' import { useMediaQuery } from '@mui/material' import { useQueryClient } from '@tanstack/react-query' import * as chrono from 'chrono-node' @@ -9,6 +18,7 @@ import { flushSync } from 'react-dom' import KeyboardShortcutHint from '../../components/common/KeyboardShortcutHint' import ModalActions from '../../components/common/ModalActions' +import { Z_INDEX } from '../../constants/zIndex' import { useDocumentScanner } from '../../hooks/useDocumentScanner' import { useFileUpload } from '../../hooks/useFileUpload' import { useResponsiveModal } from '../../hooks/useResponsiveModal' @@ -20,6 +30,7 @@ import LABEL_COLORS, { TASK_COLOR } from '../../utils/Colors' import { CreateLabel } from '../../utils/Fetcher' import { imageSourceToFile } from '../../utils/FileConvert' import { isPlusAccount } from '../../utils/Helpers' +import { getIconComponent } from '../../utils/ProjectIcons' import { generateUUID } from '../../utils/UUID' import { useLabels } from '../Labels/LabelQueries' import { useProjects } from '../Projects/ProjectQueries' @@ -122,6 +133,13 @@ const getInitialProject = () => { return 'default' } +const DEFAULT_PROJECT = { + id: 'default', + name: 'Default Project', + color: '#9CA3AF', + icon: 'FolderOpen', +} + const PRIORITY_COLORS = { 0: TASK_COLOR.NO_PRIORITY, 1: TASK_COLOR.PRIORITY_1, @@ -175,7 +193,7 @@ const TaskInput = ({ initialMode, isModalOpen, onChoreUpdate, onClose }) => { const { data: userLabels, isLoading: userLabelsLoading } = useLabels() const { data: circleMembers, isLoading: isCircleMembersLoading } = useCircleMembers() - const { isLoading: isProjectsLoading } = useProjects() + const { data: projects, isLoading: isProjectsLoading } = useProjects() const createChoreMutation = useCreateChore() const queryClient = useQueryClient() @@ -293,6 +311,17 @@ const TaskInput = ({ initialMode, isModalOpen, onChoreUpdate, onClose }) => { const [useCustomTime, setUseCustomTime] = useState(false) const [showKeyboardShortcuts, setShowKeyboardShortcuts] = useState(false) const [projectId, setProjectId] = useState(getInitialProject) + const selectedProject = useMemo( + () => + (projectId !== 'default' && + projects?.find(project => project.id === projectId)) || + DEFAULT_PROJECT, + [projects, projectId], + ) + const SelectedProjectIcon = useMemo( + () => getIconComponent(selectedProject.icon), + [selectedProject], + ) const [attachments, setAttachments] = useState([]) const [draftId, setDraftId] = useState(() => generateUUID()) @@ -1117,6 +1146,50 @@ const TaskInput = ({ initialMode, isModalOpen, onChoreUpdate, onClose }) => { title='Create new task' footer={ + {!showScan && !showVoice && projects?.length >= 1 && ( + + + } + endDecorator={} + sx={{ + mr: 'auto', + color: 'text.secondary', + fontWeight: 'normal', + }} + > + {selectedProject.name} + + + {[DEFAULT_PROJECT, ...projects].map(project => { + const ProjectIcon = getIconComponent(project.icon) + return ( + setProjectId(project.id)} + > + + + + {project.name} + + ) + })} + + + )} {activeConditions.length > 0 ? ( - <> + setSaveMenuOpen(isOpen)} + > - setSaveMenuAnchorEl(e.currentTarget)} > - + - setSaveMenuAnchorEl(null)} - placement='top-end' - sx={{ zIndex: Z_INDEX.MODAL_CONTENT + 10 }} - > + { - setSaveMenuAnchorEl(null) + setSaveMenuOpen(false) setSaveFilterName( editingSavedFilter ? `${editingSavedFilter.name} Copy` @@ -805,13 +801,13 @@ const ChoreToolbar = ({ Save as New Filter - + ) : (