From c45fb1662572652382ea791298184be59dd33cb3 Mon Sep 17 00:00:00 2001 From: chertik77 Date: Thu, 16 Jul 2026 23:31:04 +0100 Subject: [PATCH 01/39] feat(settings): add appearance settings page and menu --- .../routes/dashboard/settings/appearance.tsx | 6 ++ src/app/routes/dashboard/settings/layout.tsx | 6 ++ src/pages/settings/config/menu-data.ts | 7 ++ src/pages/settings/index.ts | 1 + src/pages/settings/ui/AppeeranceSettings.tsx | 66 +++++++++++++++++++ src/pages/settings/ui/SettigsMobileMenu.tsx | 56 ++++++++++++++++ src/pages/settings/ui/Settings.tsx | 54 +++++++++++++++ src/pages/settings/ui/SettingsPage.tsx | 34 ++++++++++ src/shared/lib/router/routeTree.gen.ts | 55 ++++++++++++++++ src/shared/ui/Switch.tsx | 21 ++++++ src/shared/ui/index.ts | 1 + src/widgets/header/ui/Header.tsx | 2 + src/widgets/header/ui/SettingsButton.tsx | 14 ++++ 13 files changed, 323 insertions(+) create mode 100644 src/app/routes/dashboard/settings/appearance.tsx create mode 100644 src/app/routes/dashboard/settings/layout.tsx create mode 100644 src/pages/settings/config/menu-data.ts create mode 100644 src/pages/settings/index.ts create mode 100644 src/pages/settings/ui/AppeeranceSettings.tsx create mode 100644 src/pages/settings/ui/SettigsMobileMenu.tsx create mode 100644 src/pages/settings/ui/Settings.tsx create mode 100644 src/pages/settings/ui/SettingsPage.tsx create mode 100644 src/shared/ui/Switch.tsx create mode 100644 src/widgets/header/ui/SettingsButton.tsx diff --git a/src/app/routes/dashboard/settings/appearance.tsx b/src/app/routes/dashboard/settings/appearance.tsx new file mode 100644 index 000000000..cf4c6cf41 --- /dev/null +++ b/src/app/routes/dashboard/settings/appearance.tsx @@ -0,0 +1,6 @@ +import { AppearanceSettings } from '@/pages/settings/ui/AppeeranceSettings' +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/dashboard/settings/appearance')({ + component: AppearanceSettings +}) diff --git a/src/app/routes/dashboard/settings/layout.tsx b/src/app/routes/dashboard/settings/layout.tsx new file mode 100644 index 000000000..8a4dd1ed4 --- /dev/null +++ b/src/app/routes/dashboard/settings/layout.tsx @@ -0,0 +1,6 @@ +import { SettingsPage } from '@/pages/settings' +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/dashboard/settings')({ + component: SettingsPage +}) diff --git a/src/pages/settings/config/menu-data.ts b/src/pages/settings/config/menu-data.ts new file mode 100644 index 000000000..59999239f --- /dev/null +++ b/src/pages/settings/config/menu-data.ts @@ -0,0 +1,7 @@ +import { Palette, UserRound, UserRoundKey } from 'lucide-react' + +export const MENU_DATA = [ + { name: 'Profile', icon: UserRound, href: '/profile' }, + { name: 'Appearance', icon: Palette, href: '/appearance' }, + { name: 'Security & Access', icon: UserRoundKey, href: '/security' } +] as const diff --git a/src/pages/settings/index.ts b/src/pages/settings/index.ts new file mode 100644 index 000000000..d72ca6908 --- /dev/null +++ b/src/pages/settings/index.ts @@ -0,0 +1 @@ +export { SettingsPage } from './ui/SettingsPage' diff --git a/src/pages/settings/ui/AppeeranceSettings.tsx b/src/pages/settings/ui/AppeeranceSettings.tsx new file mode 100644 index 000000000..7083e22eb --- /dev/null +++ b/src/pages/settings/ui/AppeeranceSettings.tsx @@ -0,0 +1,66 @@ +import { ChevronDownIcon } from 'lucide-react' + +import { THEMES } from '@/shared/config' +import { capitalize } from '@/shared/lib' +import { + Select, + SelectContent, + SelectIcon, + SelectItem, + SelectItemText, + SelectTrigger, + SelectValue, + Switch +} from '@/shared/ui' + +import { Settings } from './Settings' + +export const AppearanceSettings = () => ( + + + + Display full names + + + Which view is opened when you open up + + + + + + + + + + Display full names + + + Which view is opened when you open up + + + + + + + + +) diff --git a/src/pages/settings/ui/SettigsMobileMenu.tsx b/src/pages/settings/ui/SettigsMobileMenu.tsx new file mode 100644 index 000000000..ee3243086 --- /dev/null +++ b/src/pages/settings/ui/SettigsMobileMenu.tsx @@ -0,0 +1,56 @@ +import { Dialog } from '@base-ui/react/dialog' +import { useNavigate, useRouter } from '@tanstack/react-router' + +import { cn } from '@/shared/lib' + +import { MENU_DATA } from '../config/menu-data' + +export const SettingsMobileMenu = () => { + const { history, state } = useRouter() + + const navigate = useNavigate() + + const handleOpenChange = () => { + if (state.location.pathname === '/dashboard/settings') { + history.back() + } + } + + return ( + + + + + Settings +
    + {MENU_DATA.map(({ name, icon: Icon, href }) => ( +
  • + +
  • + ))} +
+
+
+
+ ) +} diff --git a/src/pages/settings/ui/Settings.tsx b/src/pages/settings/ui/Settings.tsx new file mode 100644 index 000000000..685f2de36 --- /dev/null +++ b/src/pages/settings/ui/Settings.tsx @@ -0,0 +1,54 @@ +import type { ReactNode } from 'react' + +import { cn } from '@/shared/lib' + +type SectionProps = { + title: string + children: ReactNode +} + +type ChildrenProps = { + children: ReactNode + className?: string +} + +const Section = ({ title, children }: SectionProps) => ( +
+

{title}

+
{children}
+
+) + +const Item = ({ children, className }: ChildrenProps) => ( +
+ {children} +
+) + +const Content = ({ children, className }: ChildrenProps) => ( +
{children}
+) + +const Title = ({ children, className }: ChildrenProps) => ( +

{children}

+) + +const Description = ({ children, className }: ChildrenProps) => ( +

+ {children} +

+) + +const Control = ({ children, className }: ChildrenProps) => ( +
{children}
+) + +export const Settings = Object.assign(Section, { + Item, + Content, + Title, + Description, + Control +}) diff --git a/src/pages/settings/ui/SettingsPage.tsx b/src/pages/settings/ui/SettingsPage.tsx new file mode 100644 index 000000000..27be95f4a --- /dev/null +++ b/src/pages/settings/ui/SettingsPage.tsx @@ -0,0 +1,34 @@ +import { Outlet, useNavigate } from '@tanstack/react-router' + +import { MENU_DATA } from '../config/menu-data' + +export const SettingsPage = () => { + const navigate = useNavigate() + + return ( +
+
+

Settings

+
    + {MENU_DATA.map(({ name, icon: Icon, href }) => ( +
  • + +
  • + ))} +
+
+
+ +
+
+ ) +} diff --git a/src/shared/lib/router/routeTree.gen.ts b/src/shared/lib/router/routeTree.gen.ts index 406d20847..bdcffaa66 100644 --- a/src/shared/lib/router/routeTree.gen.ts +++ b/src/shared/lib/router/routeTree.gen.ts @@ -14,6 +14,8 @@ import { Route as IndexRouteImport } from './../../../app/routes/index' import { Route as DashboardIndexRouteImport } from './../../../app/routes/dashboard/index' import { Route as DashboardBoardIdRouteImport } from './../../../app/routes/dashboard/$boardId' import { Route as AuthAuthLayoutRouteImport } from './../../../app/routes/auth/_auth-layout' +import { Route as DashboardSettingsLayoutRouteImport } from './../../../app/routes/dashboard/settings/layout' +import { Route as DashboardSettingsAppearanceRouteImport } from './../../../app/routes/dashboard/settings/appearance' import { Route as AuthAuthLayoutSignupRouteImport } from './../../../app/routes/auth/_auth-layout.signup' import { Route as AuthAuthLayoutSigninRouteImport } from './../../../app/routes/auth/_auth-layout.signin' @@ -42,6 +44,17 @@ const AuthAuthLayoutRoute = AuthAuthLayoutRouteImport.update({ path: '/auth', getParentRoute: () => rootRouteImport, } as any) +const DashboardSettingsLayoutRoute = DashboardSettingsLayoutRouteImport.update({ + id: '/settings', + path: '/settings', + getParentRoute: () => DashboardLayoutRoute, +} as any) +const DashboardSettingsAppearanceRoute = + DashboardSettingsAppearanceRouteImport.update({ + id: '/appearance', + path: '/appearance', + getParentRoute: () => DashboardSettingsLayoutRoute, + } as any) const AuthAuthLayoutSignupRoute = AuthAuthLayoutSignupRouteImport.update({ id: '/signup', path: '/signup', @@ -56,57 +69,69 @@ const AuthAuthLayoutSigninRoute = AuthAuthLayoutSigninRouteImport.update({ export interface FileRoutesByFullPath { '/': typeof IndexRoute '/dashboard': typeof DashboardLayoutRouteWithChildren + '/dashboard/settings': typeof DashboardSettingsLayoutRouteWithChildren '/auth': typeof AuthAuthLayoutRouteWithChildren '/dashboard/$boardId': typeof DashboardBoardIdRoute '/dashboard/': typeof DashboardIndexRoute '/auth/signin': typeof AuthAuthLayoutSigninRoute '/auth/signup': typeof AuthAuthLayoutSignupRoute + '/dashboard/settings/appearance': typeof DashboardSettingsAppearanceRoute } export interface FileRoutesByTo { '/': typeof IndexRoute + '/dashboard/settings': typeof DashboardSettingsLayoutRouteWithChildren '/auth': typeof AuthAuthLayoutRouteWithChildren '/dashboard/$boardId': typeof DashboardBoardIdRoute '/dashboard': typeof DashboardIndexRoute '/auth/signin': typeof AuthAuthLayoutSigninRoute '/auth/signup': typeof AuthAuthLayoutSignupRoute + '/dashboard/settings/appearance': typeof DashboardSettingsAppearanceRoute } export interface FileRoutesById { __root__: typeof rootRouteImport '/': typeof IndexRoute '/dashboard': typeof DashboardLayoutRouteWithChildren + '/dashboard/settings': typeof DashboardSettingsLayoutRouteWithChildren '/auth/_auth-layout': typeof AuthAuthLayoutRouteWithChildren '/dashboard/$boardId': typeof DashboardBoardIdRoute '/dashboard/': typeof DashboardIndexRoute '/auth/_auth-layout/signin': typeof AuthAuthLayoutSigninRoute '/auth/_auth-layout/signup': typeof AuthAuthLayoutSignupRoute + '/dashboard/settings/appearance': typeof DashboardSettingsAppearanceRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath fullPaths: | '/' | '/dashboard' + | '/dashboard/settings' | '/auth' | '/dashboard/$boardId' | '/dashboard/' | '/auth/signin' | '/auth/signup' + | '/dashboard/settings/appearance' fileRoutesByTo: FileRoutesByTo to: | '/' + | '/dashboard/settings' | '/auth' | '/dashboard/$boardId' | '/dashboard' | '/auth/signin' | '/auth/signup' + | '/dashboard/settings/appearance' id: | '__root__' | '/' | '/dashboard' + | '/dashboard/settings' | '/auth/_auth-layout' | '/dashboard/$boardId' | '/dashboard/' | '/auth/_auth-layout/signin' | '/auth/_auth-layout/signup' + | '/dashboard/settings/appearance' fileRoutesById: FileRoutesById } export interface RootRouteChildren { @@ -152,6 +177,20 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AuthAuthLayoutRouteImport parentRoute: typeof rootRouteImport } + '/dashboard/settings': { + id: '/dashboard/settings' + path: '/settings' + fullPath: '/dashboard/settings' + preLoaderRoute: typeof DashboardSettingsLayoutRouteImport + parentRoute: typeof DashboardLayoutRoute + } + '/dashboard/settings/appearance': { + id: '/dashboard/settings/appearance' + path: '/appearance' + fullPath: '/dashboard/settings/appearance' + preLoaderRoute: typeof DashboardSettingsAppearanceRouteImport + parentRoute: typeof DashboardSettingsLayoutRoute + } '/auth/_auth-layout/signup': { id: '/auth/_auth-layout/signup' path: '/signup' @@ -169,12 +208,28 @@ declare module '@tanstack/react-router' { } } +interface DashboardSettingsLayoutRouteChildren { + DashboardSettingsAppearanceRoute: typeof DashboardSettingsAppearanceRoute +} + +const DashboardSettingsLayoutRouteChildren: DashboardSettingsLayoutRouteChildren = + { + DashboardSettingsAppearanceRoute: DashboardSettingsAppearanceRoute, + } + +const DashboardSettingsLayoutRouteWithChildren = + DashboardSettingsLayoutRoute._addFileChildren( + DashboardSettingsLayoutRouteChildren, + ) + interface DashboardLayoutRouteChildren { + DashboardSettingsLayoutRoute: typeof DashboardSettingsLayoutRouteWithChildren DashboardBoardIdRoute: typeof DashboardBoardIdRoute DashboardIndexRoute: typeof DashboardIndexRoute } const DashboardLayoutRouteChildren: DashboardLayoutRouteChildren = { + DashboardSettingsLayoutRoute: DashboardSettingsLayoutRouteWithChildren, DashboardBoardIdRoute: DashboardBoardIdRoute, DashboardIndexRoute: DashboardIndexRoute, } diff --git a/src/shared/ui/Switch.tsx b/src/shared/ui/Switch.tsx new file mode 100644 index 000000000..e390229e4 --- /dev/null +++ b/src/shared/ui/Switch.tsx @@ -0,0 +1,21 @@ +import { Switch as SwitchPrimitive } from '@base-ui/react/switch' + +import { cn } from '../lib' + +export const Switch = ({ className, ...props }: SwitchPrimitive.Root.Props) => ( + + + +) diff --git a/src/shared/ui/index.ts b/src/shared/ui/index.ts index 0cd184562..ecd2d3060 100644 --- a/src/shared/ui/index.ts +++ b/src/shared/ui/index.ts @@ -4,6 +4,7 @@ export { TextArea } from './TextArea' export { SocialIcon } from './SocialIcon' export { PasswordInput } from './PasswordInput' export { Loader } from './Loader' +export { Switch } from './Switch' export { Checkbox } from './Checkbox' export { Avatar, AvatarImage, AvatarFallback } from './Avatar' export { PlusButtonWithLoader } from './PlusButtonWithLoader' diff --git a/src/widgets/header/ui/Header.tsx b/src/widgets/header/ui/Header.tsx index d107d62ac..b726f35e4 100644 --- a/src/widgets/header/ui/Header.tsx +++ b/src/widgets/header/ui/Header.tsx @@ -1,5 +1,6 @@ import { ThemeSelect } from '@/features/user/change-theme' +import { SettingsButton } from './SettingsButton' import { ToggleMobileMenuTrigger } from './ToggleMobileMenuTrigger' import { ToggleSidebarTrigger } from './ToggleSidebarTrigger' import { UserInfo } from './UserInfo' @@ -11,6 +12,7 @@ export const Header = () => (
+
diff --git a/src/widgets/header/ui/SettingsButton.tsx b/src/widgets/header/ui/SettingsButton.tsx new file mode 100644 index 000000000..7592da915 --- /dev/null +++ b/src/widgets/header/ui/SettingsButton.tsx @@ -0,0 +1,14 @@ +import { useNavigate } from '@tanstack/react-router' +import { SettingsIcon } from 'lucide-react' + +export const SettingsButton = () => { + const navigate = useNavigate() + + return ( + + ) +} From 5343f584cdc7c0a4f33ed8bad0fa5f0fb3e5f9ff Mon Sep 17 00:00:00 2001 From: chertik77 Date: Fri, 17 Jul 2026 03:16:13 +0100 Subject: [PATCH 02/39] feat(settings): add accessibility settings page and config --- .../dashboard/settings/accessibility.tsx | 6 ++ .../routes/dashboard/settings/appearance.tsx | 3 +- src/pages/dashboard/ui/DashboardPage.tsx | 2 +- .../settings/config/accessibility-settings.ts | 34 ++++++++++ src/pages/settings/config/menu-data.ts | 16 ++++- src/pages/settings/index.ts | 1 + src/pages/settings/model/types.ts | 7 ++ .../settings/ui/AccessibilitySettings.tsx | 21 ++++++ src/pages/settings/ui/AppeeranceSettings.tsx | 66 ------------------- src/pages/settings/ui/SettingsPage.tsx | 8 +-- src/shared/lib/router/routeTree.gen.ts | 22 +++++++ src/widgets/settings-wrapper/index.ts | 1 + .../settings-wrapper/ui/SettingsWrapper.tsx} | 26 ++++++-- src/widgets/sidebar/ui/Sidebar.tsx | 4 +- 14 files changed, 136 insertions(+), 81 deletions(-) create mode 100644 src/app/routes/dashboard/settings/accessibility.tsx create mode 100644 src/pages/settings/config/accessibility-settings.ts create mode 100644 src/pages/settings/model/types.ts create mode 100644 src/pages/settings/ui/AccessibilitySettings.tsx delete mode 100644 src/pages/settings/ui/AppeeranceSettings.tsx create mode 100644 src/widgets/settings-wrapper/index.ts rename src/{pages/settings/ui/Settings.tsx => widgets/settings-wrapper/ui/SettingsWrapper.tsx} (52%) diff --git a/src/app/routes/dashboard/settings/accessibility.tsx b/src/app/routes/dashboard/settings/accessibility.tsx new file mode 100644 index 000000000..83dd8ad52 --- /dev/null +++ b/src/app/routes/dashboard/settings/accessibility.tsx @@ -0,0 +1,6 @@ +import { AccessibilitySettings } from '@/pages/settings' +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/dashboard/settings/accessibility')({ + component: AccessibilitySettings +}) diff --git a/src/app/routes/dashboard/settings/appearance.tsx b/src/app/routes/dashboard/settings/appearance.tsx index cf4c6cf41..27bdbf502 100644 --- a/src/app/routes/dashboard/settings/appearance.tsx +++ b/src/app/routes/dashboard/settings/appearance.tsx @@ -1,6 +1,5 @@ -import { AppearanceSettings } from '@/pages/settings/ui/AppeeranceSettings' import { createFileRoute } from '@tanstack/react-router' export const Route = createFileRoute('/dashboard/settings/appearance')({ - component: AppearanceSettings + component: () =>
ewdewd
}) diff --git a/src/pages/dashboard/ui/DashboardPage.tsx b/src/pages/dashboard/ui/DashboardPage.tsx index bc970ed0b..a9d35f861 100644 --- a/src/pages/dashboard/ui/DashboardPage.tsx +++ b/src/pages/dashboard/ui/DashboardPage.tsx @@ -21,7 +21,7 @@ export const DashboardPage = () => { return (
diff --git a/src/pages/settings/config/accessibility-settings.ts b/src/pages/settings/config/accessibility-settings.ts new file mode 100644 index 000000000..c55303788 --- /dev/null +++ b/src/pages/settings/config/accessibility-settings.ts @@ -0,0 +1,34 @@ +import type { Setting } from '../model/types' + +import { Switch } from '@/shared/ui' + +export const ACCESSIBILITY_SETTINGS: Setting[] = [ + { + title: 'Font size', + description: 'Adjust the text size across the app for better readability.', + control: Switch + }, + { + title: 'Reduced motion', + description: + 'Minimize animations and transitions to reduce visual movement.', + control: Switch + }, + { + title: 'High contrast', + description: + 'Increase contrast between interface elements for better visibility.', + control: Switch + }, + { + title: 'Focus indicators', + description: 'Show clear outlines around focused elements when navigating.', + control: Switch + }, + { + title: 'Keyboard navigation hints', + description: + 'Display shortcuts and hints to help navigate with your keyboard.', + control: Switch + } +] as const diff --git a/src/pages/settings/config/menu-data.ts b/src/pages/settings/config/menu-data.ts index 59999239f..eb2f59f05 100644 --- a/src/pages/settings/config/menu-data.ts +++ b/src/pages/settings/config/menu-data.ts @@ -1,7 +1,19 @@ -import { Palette, UserRound, UserRoundKey } from 'lucide-react' +import { + ListTodo, + Palette, + PersonStanding, + Settings2, + Tag, + UserRound, + UserRoundKey +} from 'lucide-react' export const MENU_DATA = [ + { name: 'General', icon: Settings2, href: '/general' }, { name: 'Profile', icon: UserRound, href: '/profile' }, { name: 'Appearance', icon: Palette, href: '/appearance' }, - { name: 'Security & Access', icon: UserRoundKey, href: '/security' } + { name: 'Labels', icon: Tag, href: '/labels' }, + { name: 'Task Preferences', icon: ListTodo, href: '/task' }, + { name: 'Security & Access', icon: UserRoundKey, href: '/security' }, + { name: 'Accessibility', icon: PersonStanding, href: '/accessibility' } ] as const diff --git a/src/pages/settings/index.ts b/src/pages/settings/index.ts index d72ca6908..7d08fb6db 100644 --- a/src/pages/settings/index.ts +++ b/src/pages/settings/index.ts @@ -1 +1,2 @@ export { SettingsPage } from './ui/SettingsPage' +export { AccessibilitySettings } from './ui/AccessibilitySettings' diff --git a/src/pages/settings/model/types.ts b/src/pages/settings/model/types.ts new file mode 100644 index 000000000..1a891a92c --- /dev/null +++ b/src/pages/settings/model/types.ts @@ -0,0 +1,7 @@ +import type { ComponentType } from 'react' + +export type Setting = { + title: string + description: string + control: ComponentType +} diff --git a/src/pages/settings/ui/AccessibilitySettings.tsx b/src/pages/settings/ui/AccessibilitySettings.tsx new file mode 100644 index 000000000..d53f8acf9 --- /dev/null +++ b/src/pages/settings/ui/AccessibilitySettings.tsx @@ -0,0 +1,21 @@ +import { SettingsWrapper } from '@/widgets/settings-wrapper' + +import { ACCESSIBILITY_SETTINGS } from '../config/accessibility-settings' + +export const AccessibilitySettings = () => ( + + {ACCESSIBILITY_SETTINGS.map(({ title, description, control: Control }) => ( + + + {title} + + {description} + + + + + + + ))} + +) diff --git a/src/pages/settings/ui/AppeeranceSettings.tsx b/src/pages/settings/ui/AppeeranceSettings.tsx deleted file mode 100644 index 7083e22eb..000000000 --- a/src/pages/settings/ui/AppeeranceSettings.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { ChevronDownIcon } from 'lucide-react' - -import { THEMES } from '@/shared/config' -import { capitalize } from '@/shared/lib' -import { - Select, - SelectContent, - SelectIcon, - SelectItem, - SelectItemText, - SelectTrigger, - SelectValue, - Switch -} from '@/shared/ui' - -import { Settings } from './Settings' - -export const AppearanceSettings = () => ( - - - - Display full names - - - Which view is opened when you open up - - - - - - - - - - Display full names - - - Which view is opened when you open up - - - - - - - - -) diff --git a/src/pages/settings/ui/SettingsPage.tsx b/src/pages/settings/ui/SettingsPage.tsx index 27be95f4a..741fcf4bb 100644 --- a/src/pages/settings/ui/SettingsPage.tsx +++ b/src/pages/settings/ui/SettingsPage.tsx @@ -9,16 +9,16 @@ export const SettingsPage = () => {
+ border border-b-0 border-l-0 px-6 py-20 dark:bg-black'>

Settings

    {MENU_DATA.map(({ name, icon: Icon, href }) => (
  • diff --git a/src/shared/lib/router/routeTree.gen.ts b/src/shared/lib/router/routeTree.gen.ts index bdcffaa66..de2b770c9 100644 --- a/src/shared/lib/router/routeTree.gen.ts +++ b/src/shared/lib/router/routeTree.gen.ts @@ -16,6 +16,7 @@ import { Route as DashboardBoardIdRouteImport } from './../../../app/routes/dash import { Route as AuthAuthLayoutRouteImport } from './../../../app/routes/auth/_auth-layout' import { Route as DashboardSettingsLayoutRouteImport } from './../../../app/routes/dashboard/settings/layout' import { Route as DashboardSettingsAppearanceRouteImport } from './../../../app/routes/dashboard/settings/appearance' +import { Route as DashboardSettingsAccessibilityRouteImport } from './../../../app/routes/dashboard/settings/accessibility' import { Route as AuthAuthLayoutSignupRouteImport } from './../../../app/routes/auth/_auth-layout.signup' import { Route as AuthAuthLayoutSigninRouteImport } from './../../../app/routes/auth/_auth-layout.signin' @@ -55,6 +56,12 @@ const DashboardSettingsAppearanceRoute = path: '/appearance', getParentRoute: () => DashboardSettingsLayoutRoute, } as any) +const DashboardSettingsAccessibilityRoute = + DashboardSettingsAccessibilityRouteImport.update({ + id: '/accessibility', + path: '/accessibility', + getParentRoute: () => DashboardSettingsLayoutRoute, + } as any) const AuthAuthLayoutSignupRoute = AuthAuthLayoutSignupRouteImport.update({ id: '/signup', path: '/signup', @@ -75,6 +82,7 @@ export interface FileRoutesByFullPath { '/dashboard/': typeof DashboardIndexRoute '/auth/signin': typeof AuthAuthLayoutSigninRoute '/auth/signup': typeof AuthAuthLayoutSignupRoute + '/dashboard/settings/accessibility': typeof DashboardSettingsAccessibilityRoute '/dashboard/settings/appearance': typeof DashboardSettingsAppearanceRoute } export interface FileRoutesByTo { @@ -85,6 +93,7 @@ export interface FileRoutesByTo { '/dashboard': typeof DashboardIndexRoute '/auth/signin': typeof AuthAuthLayoutSigninRoute '/auth/signup': typeof AuthAuthLayoutSignupRoute + '/dashboard/settings/accessibility': typeof DashboardSettingsAccessibilityRoute '/dashboard/settings/appearance': typeof DashboardSettingsAppearanceRoute } export interface FileRoutesById { @@ -97,6 +106,7 @@ export interface FileRoutesById { '/dashboard/': typeof DashboardIndexRoute '/auth/_auth-layout/signin': typeof AuthAuthLayoutSigninRoute '/auth/_auth-layout/signup': typeof AuthAuthLayoutSignupRoute + '/dashboard/settings/accessibility': typeof DashboardSettingsAccessibilityRoute '/dashboard/settings/appearance': typeof DashboardSettingsAppearanceRoute } export interface FileRouteTypes { @@ -110,6 +120,7 @@ export interface FileRouteTypes { | '/dashboard/' | '/auth/signin' | '/auth/signup' + | '/dashboard/settings/accessibility' | '/dashboard/settings/appearance' fileRoutesByTo: FileRoutesByTo to: @@ -120,6 +131,7 @@ export interface FileRouteTypes { | '/dashboard' | '/auth/signin' | '/auth/signup' + | '/dashboard/settings/accessibility' | '/dashboard/settings/appearance' id: | '__root__' @@ -131,6 +143,7 @@ export interface FileRouteTypes { | '/dashboard/' | '/auth/_auth-layout/signin' | '/auth/_auth-layout/signup' + | '/dashboard/settings/accessibility' | '/dashboard/settings/appearance' fileRoutesById: FileRoutesById } @@ -191,6 +204,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof DashboardSettingsAppearanceRouteImport parentRoute: typeof DashboardSettingsLayoutRoute } + '/dashboard/settings/accessibility': { + id: '/dashboard/settings/accessibility' + path: '/accessibility' + fullPath: '/dashboard/settings/accessibility' + preLoaderRoute: typeof DashboardSettingsAccessibilityRouteImport + parentRoute: typeof DashboardSettingsLayoutRoute + } '/auth/_auth-layout/signup': { id: '/auth/_auth-layout/signup' path: '/signup' @@ -209,11 +229,13 @@ declare module '@tanstack/react-router' { } interface DashboardSettingsLayoutRouteChildren { + DashboardSettingsAccessibilityRoute: typeof DashboardSettingsAccessibilityRoute DashboardSettingsAppearanceRoute: typeof DashboardSettingsAppearanceRoute } const DashboardSettingsLayoutRouteChildren: DashboardSettingsLayoutRouteChildren = { + DashboardSettingsAccessibilityRoute: DashboardSettingsAccessibilityRoute, DashboardSettingsAppearanceRoute: DashboardSettingsAppearanceRoute, } diff --git a/src/widgets/settings-wrapper/index.ts b/src/widgets/settings-wrapper/index.ts new file mode 100644 index 000000000..b2f605722 --- /dev/null +++ b/src/widgets/settings-wrapper/index.ts @@ -0,0 +1 @@ +export { SettingsWrapper } from './ui/SettingsWrapper' diff --git a/src/pages/settings/ui/Settings.tsx b/src/widgets/settings-wrapper/ui/SettingsWrapper.tsx similarity index 52% rename from src/pages/settings/ui/Settings.tsx rename to src/widgets/settings-wrapper/ui/SettingsWrapper.tsx index 685f2de36..2ec8636c7 100644 --- a/src/pages/settings/ui/Settings.tsx +++ b/src/widgets/settings-wrapper/ui/SettingsWrapper.tsx @@ -1,5 +1,7 @@ import type { ReactNode } from 'react' +import { ScrollArea } from '@base-ui/react/scroll-area' + import { cn } from '@/shared/lib' type SectionProps = { @@ -14,10 +16,24 @@ type ChildrenProps = { const Section = ({ title, children }: SectionProps) => (
    -

    {title}

    -
    {children}
    + className='bg-white-soft border-brand dark:border-brand/50 flex h-full + flex-col rounded-lg border p-6 dark:bg-black'> +

    {title}

    + + + +
    {children}
    +
    +
    + + + +
    ) @@ -45,7 +61,7 @@ const Control = ({ children, className }: ChildrenProps) => (
    {children}
    ) -export const Settings = Object.assign(Section, { +export const SettingsWrapper = Object.assign(Section, { Item, Content, Title, diff --git a/src/widgets/sidebar/ui/Sidebar.tsx b/src/widgets/sidebar/ui/Sidebar.tsx index 01653b6fe..74efecb2c 100644 --- a/src/widgets/sidebar/ui/Sidebar.tsx +++ b/src/widgets/sidebar/ui/Sidebar.tsx @@ -46,7 +46,9 @@ export const Sidebar = () => { className='pointer-events-none w-1 bg-transparent opacity-0 transition-opacity duration-200 data-scrolling:pointer-events-auto data-scrolling:opacity-100 data-scrolling:duration-initial'> - + From 658caa57f732841b0f019055801a0f77b7904729 Mon Sep 17 00:00:00 2001 From: chertik77 Date: Fri, 17 Jul 2026 14:45:55 +0100 Subject: [PATCH 03/39] feat(settings): add general settings page and api --- .../dashboard/settings/accessibility.tsx | 3 +- src/app/routes/dashboard/settings/general.tsx | 7 + src/app/routes/dashboard/settings/layout.tsx | 1 + src/entities/settings/index.ts | 2 + .../settings/ui/Settings.tsx} | 47 ++++-- .../general/api/useGeneralSettings.ts | 9 + .../general/api/useUpdateGeneralSettings.ts | 6 + .../general/config/general-settings.ts | 28 +++ src/features/settings/general/index.ts | 1 + .../settings/general/ui/GeneralSettings.tsx | 64 +++++++ .../settings/config/accessibility-settings.ts | 34 ---- src/pages/settings/index.ts | 1 - src/pages/settings/model/types.ts | 7 - .../settings/ui/AccessibilitySettings.tsx | 21 --- .../generated/@tanstack/react-query.gen.ts | 90 +++++++++- src/shared/api/generated/index.ts | 34 ++++ src/shared/api/generated/sdk.gen.ts | 68 +++++++- src/shared/api/generated/types.gen.ts | 159 ++++++++++++++++++ src/shared/lib/router/routeTree.gen.ts | 22 +++ src/shared/ui/Loader.tsx | 22 +-- src/widgets/settings-wrapper/index.ts | 1 - 21 files changed, 530 insertions(+), 97 deletions(-) create mode 100644 src/app/routes/dashboard/settings/general.tsx create mode 100644 src/entities/settings/index.ts rename src/{widgets/settings-wrapper/ui/SettingsWrapper.tsx => entities/settings/ui/Settings.tsx} (50%) create mode 100644 src/features/settings/general/api/useGeneralSettings.ts create mode 100644 src/features/settings/general/api/useUpdateGeneralSettings.ts create mode 100644 src/features/settings/general/config/general-settings.ts create mode 100644 src/features/settings/general/index.ts create mode 100644 src/features/settings/general/ui/GeneralSettings.tsx delete mode 100644 src/pages/settings/config/accessibility-settings.ts delete mode 100644 src/pages/settings/model/types.ts delete mode 100644 src/pages/settings/ui/AccessibilitySettings.tsx delete mode 100644 src/widgets/settings-wrapper/index.ts diff --git a/src/app/routes/dashboard/settings/accessibility.tsx b/src/app/routes/dashboard/settings/accessibility.tsx index 83dd8ad52..e1c2aa556 100644 --- a/src/app/routes/dashboard/settings/accessibility.tsx +++ b/src/app/routes/dashboard/settings/accessibility.tsx @@ -1,6 +1,5 @@ -import { AccessibilitySettings } from '@/pages/settings' import { createFileRoute } from '@tanstack/react-router' export const Route = createFileRoute('/dashboard/settings/accessibility')({ - component: AccessibilitySettings + component: () =>
    ewdewdwed
    }) diff --git a/src/app/routes/dashboard/settings/general.tsx b/src/app/routes/dashboard/settings/general.tsx new file mode 100644 index 000000000..c5e87f123 --- /dev/null +++ b/src/app/routes/dashboard/settings/general.tsx @@ -0,0 +1,7 @@ +import { createFileRoute } from '@tanstack/react-router' + +import { GeneralSettings } from '@/features/settings/general' + +export const Route = createFileRoute('/dashboard/settings/general')({ + component: GeneralSettings +}) diff --git a/src/app/routes/dashboard/settings/layout.tsx b/src/app/routes/dashboard/settings/layout.tsx index 8a4dd1ed4..8dc2c9428 100644 --- a/src/app/routes/dashboard/settings/layout.tsx +++ b/src/app/routes/dashboard/settings/layout.tsx @@ -3,4 +3,5 @@ import { createFileRoute } from '@tanstack/react-router' export const Route = createFileRoute('/dashboard/settings')({ component: SettingsPage + // loader: async () => await queryClient.ensureQueryData(getAllSettingsOptions()) }) diff --git a/src/entities/settings/index.ts b/src/entities/settings/index.ts new file mode 100644 index 000000000..562b5a987 --- /dev/null +++ b/src/entities/settings/index.ts @@ -0,0 +1,2 @@ +export { Settings } from './ui/Settings' +export { SwitchControl } from './ui/controls/SwitchControl' diff --git a/src/widgets/settings-wrapper/ui/SettingsWrapper.tsx b/src/entities/settings/ui/Settings.tsx similarity index 50% rename from src/widgets/settings-wrapper/ui/SettingsWrapper.tsx rename to src/entities/settings/ui/Settings.tsx index 2ec8636c7..5a54ec8f5 100644 --- a/src/widgets/settings-wrapper/ui/SettingsWrapper.tsx +++ b/src/entities/settings/ui/Settings.tsx @@ -3,10 +3,12 @@ import type { ReactNode } from 'react' import { ScrollArea } from '@base-ui/react/scroll-area' import { cn } from '@/shared/lib' +import { Loader } from '@/shared/ui' type SectionProps = { title: string children: ReactNode + isLoading?: boolean } type ChildrenProps = { @@ -14,26 +16,35 @@ type ChildrenProps = { className?: string } -const Section = ({ title, children }: SectionProps) => ( +const Section = ({ title, children, isLoading }: SectionProps) => (
    -

    {title}

    - - - -
    {children}
    -
    -
    - - - -
    + {isLoading ? ( +
    + + Loading... +
    + ) : ( + <> +

    {title}

    + + + +
    {children}
    +
    +
    + + + +
    + + )}
    ) @@ -61,7 +72,7 @@ const Control = ({ children, className }: ChildrenProps) => (
    {children}
    ) -export const SettingsWrapper = Object.assign(Section, { +export const Settings = Object.assign(Section, { Item, Content, Title, diff --git a/src/features/settings/general/api/useGeneralSettings.ts b/src/features/settings/general/api/useGeneralSettings.ts new file mode 100644 index 000000000..d12f1078b --- /dev/null +++ b/src/features/settings/general/api/useGeneralSettings.ts @@ -0,0 +1,9 @@ +import { useQuery } from '@tanstack/react-query' + +import { getAllSettingsOptions } from '@/shared/api' + +export const useGeneralSettings = () => + useQuery({ + ...getAllSettingsOptions(), + select: settings => settings.general + }) diff --git a/src/features/settings/general/api/useUpdateGeneralSettings.ts b/src/features/settings/general/api/useUpdateGeneralSettings.ts new file mode 100644 index 000000000..58e583540 --- /dev/null +++ b/src/features/settings/general/api/useUpdateGeneralSettings.ts @@ -0,0 +1,6 @@ +import { useMutation } from '@tanstack/react-query' + +import { updateGeneralSettingsMutation } from '@/shared/api' + +export const useUpdateGeneralSettings = () => + useMutation(updateGeneralSettingsMutation()) diff --git a/src/features/settings/general/config/general-settings.ts b/src/features/settings/general/config/general-settings.ts new file mode 100644 index 000000000..09da99192 --- /dev/null +++ b/src/features/settings/general/config/general-settings.ts @@ -0,0 +1,28 @@ +import type { SettingsTypes } from '@/entities/settings' + +import { SelectControl } from '@/entities/settings' + +export const GENERAl_SETTINGS: SettingsTypes.Setting[] = [ + { + key: 'theme', + title: 'Theme', + description: 'Choose between light, dark, or system appearance.', + Control: SelectControl, + controlProps: { + options: [ + { + label: 'Small', + value: 'small' + }, + { + label: 'Medium', + value: 'medium' + }, + { + label: 'Large', + value: 'large' + } + ] + } + } +] as const diff --git a/src/features/settings/general/index.ts b/src/features/settings/general/index.ts new file mode 100644 index 000000000..68021e0db --- /dev/null +++ b/src/features/settings/general/index.ts @@ -0,0 +1 @@ +export { GeneralSettings } from './ui/GeneralSettings' diff --git a/src/features/settings/general/ui/GeneralSettings.tsx b/src/features/settings/general/ui/GeneralSettings.tsx new file mode 100644 index 000000000..58bb18e33 --- /dev/null +++ b/src/features/settings/general/ui/GeneralSettings.tsx @@ -0,0 +1,64 @@ +import { ChevronDownIcon } from 'lucide-react' + +import { Settings } from '@/entities/settings' + +import { THEMES } from '@/shared/config' +import { capitalize } from '@/shared/lib' +import { + Select, + SelectContent, + SelectIcon, + SelectItem, + SelectItemText, + SelectTrigger, + SelectValue +} from '@/shared/ui' + +import { useGeneralSettings } from '../api/useGeneralSettings' +import { useUpdateGeneralSettings } from '../api/useUpdateGeneralSettings' + +export const GeneralSettings = () => { + const { data, isPending } = useGeneralSettings() + + const { mutate: update } = useUpdateGeneralSettings() + + return ( + + + + Theme + + Choose between light, dark, or system appearance. + + + + + + + + ) +} diff --git a/src/pages/settings/config/accessibility-settings.ts b/src/pages/settings/config/accessibility-settings.ts deleted file mode 100644 index c55303788..000000000 --- a/src/pages/settings/config/accessibility-settings.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type { Setting } from '../model/types' - -import { Switch } from '@/shared/ui' - -export const ACCESSIBILITY_SETTINGS: Setting[] = [ - { - title: 'Font size', - description: 'Adjust the text size across the app for better readability.', - control: Switch - }, - { - title: 'Reduced motion', - description: - 'Minimize animations and transitions to reduce visual movement.', - control: Switch - }, - { - title: 'High contrast', - description: - 'Increase contrast between interface elements for better visibility.', - control: Switch - }, - { - title: 'Focus indicators', - description: 'Show clear outlines around focused elements when navigating.', - control: Switch - }, - { - title: 'Keyboard navigation hints', - description: - 'Display shortcuts and hints to help navigate with your keyboard.', - control: Switch - } -] as const diff --git a/src/pages/settings/index.ts b/src/pages/settings/index.ts index 7d08fb6db..d72ca6908 100644 --- a/src/pages/settings/index.ts +++ b/src/pages/settings/index.ts @@ -1,2 +1 @@ export { SettingsPage } from './ui/SettingsPage' -export { AccessibilitySettings } from './ui/AccessibilitySettings' diff --git a/src/pages/settings/model/types.ts b/src/pages/settings/model/types.ts deleted file mode 100644 index 1a891a92c..000000000 --- a/src/pages/settings/model/types.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { ComponentType } from 'react' - -export type Setting = { - title: string - description: string - control: ComponentType -} diff --git a/src/pages/settings/ui/AccessibilitySettings.tsx b/src/pages/settings/ui/AccessibilitySettings.tsx deleted file mode 100644 index d53f8acf9..000000000 --- a/src/pages/settings/ui/AccessibilitySettings.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { SettingsWrapper } from '@/widgets/settings-wrapper' - -import { ACCESSIBILITY_SETTINGS } from '../config/accessibility-settings' - -export const AccessibilitySettings = () => ( - - {ACCESSIBILITY_SETTINGS.map(({ title, description, control: Control }) => ( - - - {title} - - {description} - - - - - - - ))} - -) diff --git a/src/shared/api/generated/@tanstack/react-query.gen.ts b/src/shared/api/generated/@tanstack/react-query.gen.ts index 459f33b6b..0a0c8f973 100644 --- a/src/shared/api/generated/@tanstack/react-query.gen.ts +++ b/src/shared/api/generated/@tanstack/react-query.gen.ts @@ -4,8 +4,8 @@ import { queryOptions, type UseMutationOptions } from '@tanstack/react-query'; import type { AxiosError } from 'axios'; import { client } from '../client.gen'; -import { createBoard, createColumn, createLabel, createTask, deleteBoard, deleteColumn, deleteLabel, deleteTask, getAllBoards, getAllLabels, getBoardById, help, type Options, updateBoard, updateColumn, updateColumnsOrder, updateLabel, updateTask, updateTasksOrder } from '../sdk.gen'; -import type { CreateBoardData, CreateBoardError, CreateBoardResponse, CreateColumnData, CreateColumnError, CreateColumnResponse, CreateLabelData, CreateLabelError, CreateLabelResponse, CreateTaskData, CreateTaskError, CreateTaskResponse, DeleteBoardData, DeleteBoardError, DeleteBoardResponse, DeleteColumnData, DeleteColumnError, DeleteColumnResponse, DeleteLabelData, DeleteLabelError, DeleteLabelResponse, DeleteTaskData, DeleteTaskError, DeleteTaskResponse, GetAllBoardsData, GetAllBoardsError, GetAllBoardsResponse, GetAllLabelsData, GetAllLabelsError, GetAllLabelsResponse, GetBoardByIdData, GetBoardByIdError, GetBoardByIdResponse, HelpData, HelpError, HelpResponse, UpdateBoardData, UpdateBoardError, UpdateBoardResponse, UpdateColumnData, UpdateColumnError, UpdateColumnResponse, UpdateColumnsOrderData, UpdateColumnsOrderError, UpdateColumnsOrderResponse, UpdateLabelData, UpdateLabelError, UpdateLabelResponse, UpdateTaskData, UpdateTaskError, UpdateTaskResponse, UpdateTasksOrderData, UpdateTasksOrderError, UpdateTasksOrderResponse } from '../types.gen'; +import { createBoard, createColumn, createLabel, createTask, deleteBoard, deleteColumn, deleteLabel, deleteTask, getAllBoards, getAllLabels, getAllSettings, getBoardById, help, type Options, updateAccessibilitySettings, updateBoard, updateColumn, updateColumnsOrder, updateGeneralSettings, updateLabel, updateLabelSettings, updateTask, updateTaskSettings, updateTasksOrder } from '../sdk.gen'; +import type { CreateBoardData, CreateBoardError, CreateBoardResponse, CreateColumnData, CreateColumnError, CreateColumnResponse, CreateLabelData, CreateLabelError, CreateLabelResponse, CreateTaskData, CreateTaskError, CreateTaskResponse, DeleteBoardData, DeleteBoardError, DeleteBoardResponse, DeleteColumnData, DeleteColumnError, DeleteColumnResponse, DeleteLabelData, DeleteLabelError, DeleteLabelResponse, DeleteTaskData, DeleteTaskError, DeleteTaskResponse, GetAllBoardsData, GetAllBoardsError, GetAllBoardsResponse, GetAllLabelsData, GetAllLabelsError, GetAllLabelsResponse, GetAllSettingsData, GetAllSettingsError, GetAllSettingsResponse, GetBoardByIdData, GetBoardByIdError, GetBoardByIdResponse, HelpData, HelpError, HelpResponse, UpdateAccessibilitySettingsData, UpdateAccessibilitySettingsError, UpdateAccessibilitySettingsResponse, UpdateBoardData, UpdateBoardError, UpdateBoardResponse, UpdateColumnData, UpdateColumnError, UpdateColumnResponse, UpdateColumnsOrderData, UpdateColumnsOrderError, UpdateColumnsOrderResponse, UpdateGeneralSettingsData, UpdateGeneralSettingsError, UpdateGeneralSettingsResponse, UpdateLabelData, UpdateLabelError, UpdateLabelResponse, UpdateLabelSettingsData, UpdateLabelSettingsError, UpdateLabelSettingsResponse, UpdateTaskData, UpdateTaskError, UpdateTaskResponse, UpdateTaskSettingsData, UpdateTaskSettingsError, UpdateTaskSettingsResponse, UpdateTasksOrderData, UpdateTasksOrderError, UpdateTasksOrderResponse } from '../types.gen'; /** * Send email need help @@ -57,6 +57,92 @@ const createQueryKey = (id: string, options?: TOptions return [params]; }; +export const getAllSettingsQueryKey = (options?: Options) => createQueryKey('getAllSettings', options); + +/** + * Get all user settings + */ +export const getAllSettingsOptions = (options?: Options) => queryOptions, GetAllSettingsResponse, ReturnType>({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await getAllSettings({ + ...options, + ...queryKey[0], + signal, + throwOnError: true + }); + return data; + }, + queryKey: getAllSettingsQueryKey(options) +}); + +/** + * Update general settings + */ +export const updateGeneralSettingsMutation = (options?: Partial>): UseMutationOptions, Options> => { + const mutationOptions: UseMutationOptions, Options> = { + mutationFn: async (fnOptions) => { + const { data } = await updateGeneralSettings({ + ...options, + ...fnOptions, + throwOnError: true + }); + return data; + } + }; + return mutationOptions; +}; + +/** + * Update task settings + */ +export const updateTaskSettingsMutation = (options?: Partial>): UseMutationOptions, Options> => { + const mutationOptions: UseMutationOptions, Options> = { + mutationFn: async (fnOptions) => { + const { data } = await updateTaskSettings({ + ...options, + ...fnOptions, + throwOnError: true + }); + return data; + } + }; + return mutationOptions; +}; + +/** + * Update label settings + */ +export const updateLabelSettingsMutation = (options?: Partial>): UseMutationOptions, Options> => { + const mutationOptions: UseMutationOptions, Options> = { + mutationFn: async (fnOptions) => { + const { data } = await updateLabelSettings({ + ...options, + ...fnOptions, + throwOnError: true + }); + return data; + } + }; + return mutationOptions; +}; + +/** + * Update accessibility settings + */ +export const updateAccessibilitySettingsMutation = (options?: Partial>): UseMutationOptions, Options> => { + const mutationOptions: UseMutationOptions, Options> = { + mutationFn: async (fnOptions) => { + const { data } = await updateAccessibilitySettings({ + ...options, + ...fnOptions, + throwOnError: true + }); + return data; + } + }; + return mutationOptions; +}; + export const getAllBoardsQueryKey = (options?: Options) => createQueryKey('getAllBoards', options); /** diff --git a/src/shared/api/generated/index.ts b/src/shared/api/generated/index.ts index 6a8d2bf7b..74a36a121 100644 --- a/src/shared/api/generated/index.ts +++ b/src/shared/api/generated/index.ts @@ -11,17 +11,23 @@ export { deleteTask, getAllBoards, getAllLabels, + getAllSettings, getBoardById, help, type Options, + updateAccessibilitySettings, updateBoard, updateColumn, updateColumnsOrder, + updateGeneralSettings, updateLabel, + updateLabelSettings, updateTask, + updateTaskSettings, updateTasksOrder } from './sdk.gen' export { + type AccessibilitySettings, type Board, type BoardBackground, BoardBackgroundId, @@ -69,6 +75,7 @@ export { type DeleteTaskResponse, type DeleteTaskResponses, type ErrorResponse, + type GeneralSettings, type GetAllBoardsData, type GetAllBoardsError, type GetAllBoardsErrors, @@ -79,6 +86,11 @@ export { type GetAllLabelsErrors, type GetAllLabelsResponse, type GetAllLabelsResponses, + type GetAllSettingsData, + type GetAllSettingsError, + type GetAllSettingsErrors, + type GetAllSettingsResponse, + type GetAllSettingsResponses, type GetBoardByIdData, type GetBoardByIdError, type GetBoardByIdErrors, @@ -91,8 +103,15 @@ export { type HelpResponses, type Label, LabelColor, + type LabelSettings, type Task, TaskPriority, + type TaskSettings, + type UpdateAccessibilitySettingsData, + type UpdateAccessibilitySettingsError, + type UpdateAccessibilitySettingsErrors, + type UpdateAccessibilitySettingsResponse, + type UpdateAccessibilitySettingsResponses, type UpdateBoardData, type UpdateBoardError, type UpdateBoardErrors, @@ -108,16 +127,31 @@ export { type UpdateColumnsOrderErrors, type UpdateColumnsOrderResponse, type UpdateColumnsOrderResponses, + type UpdateGeneralSettingsData, + type UpdateGeneralSettingsError, + type UpdateGeneralSettingsErrors, + type UpdateGeneralSettingsResponse, + type UpdateGeneralSettingsResponses, type UpdateLabelData, type UpdateLabelError, type UpdateLabelErrors, type UpdateLabelResponse, type UpdateLabelResponses, + type UpdateLabelSettingsData, + type UpdateLabelSettingsError, + type UpdateLabelSettingsErrors, + type UpdateLabelSettingsResponse, + type UpdateLabelSettingsResponses, type UpdateTaskData, type UpdateTaskError, type UpdateTaskErrors, type UpdateTaskResponse, type UpdateTaskResponses, + type UpdateTaskSettingsData, + type UpdateTaskSettingsError, + type UpdateTaskSettingsErrors, + type UpdateTaskSettingsResponse, + type UpdateTaskSettingsResponses, type UpdateTasksOrderData, type UpdateTasksOrderError, type UpdateTasksOrderErrors, diff --git a/src/shared/api/generated/sdk.gen.ts b/src/shared/api/generated/sdk.gen.ts index 8335a3183..4b2e5b8e0 100644 --- a/src/shared/api/generated/sdk.gen.ts +++ b/src/shared/api/generated/sdk.gen.ts @@ -3,7 +3,7 @@ import type { Client, ClientMeta, Options as Options2, RequestResult, TDataShape } from './client'; import { client } from './client.gen'; import { createBoardResponseTransformer, createColumnResponseTransformer, createLabelResponseTransformer, createTaskResponseTransformer, getAllBoardsResponseTransformer, getAllLabelsResponseTransformer, getBoardByIdResponseTransformer, updateBoardResponseTransformer, updateColumnResponseTransformer, updateColumnsOrderResponseTransformer, updateLabelResponseTransformer, updateTaskResponseTransformer, updateTasksOrderResponseTransformer } from './transformers.gen'; -import type { CreateBoardData, CreateBoardErrors, CreateBoardResponses, CreateColumnData, CreateColumnErrors, CreateColumnResponses, CreateLabelData, CreateLabelErrors, CreateLabelResponses, CreateTaskData, CreateTaskErrors, CreateTaskResponses, DeleteBoardData, DeleteBoardErrors, DeleteBoardResponses, DeleteColumnData, DeleteColumnErrors, DeleteColumnResponses, DeleteLabelData, DeleteLabelErrors, DeleteLabelResponses, DeleteTaskData, DeleteTaskErrors, DeleteTaskResponses, GetAllBoardsData, GetAllBoardsErrors, GetAllBoardsResponses, GetAllLabelsData, GetAllLabelsErrors, GetAllLabelsResponses, GetBoardByIdData, GetBoardByIdErrors, GetBoardByIdResponses, HelpData, HelpErrors, HelpResponses, UpdateBoardData, UpdateBoardErrors, UpdateBoardResponses, UpdateColumnData, UpdateColumnErrors, UpdateColumnResponses, UpdateColumnsOrderData, UpdateColumnsOrderErrors, UpdateColumnsOrderResponses, UpdateLabelData, UpdateLabelErrors, UpdateLabelResponses, UpdateTaskData, UpdateTaskErrors, UpdateTaskResponses, UpdateTasksOrderData, UpdateTasksOrderErrors, UpdateTasksOrderResponses } from './types.gen'; +import type { CreateBoardData, CreateBoardErrors, CreateBoardResponses, CreateColumnData, CreateColumnErrors, CreateColumnResponses, CreateLabelData, CreateLabelErrors, CreateLabelResponses, CreateTaskData, CreateTaskErrors, CreateTaskResponses, DeleteBoardData, DeleteBoardErrors, DeleteBoardResponses, DeleteColumnData, DeleteColumnErrors, DeleteColumnResponses, DeleteLabelData, DeleteLabelErrors, DeleteLabelResponses, DeleteTaskData, DeleteTaskErrors, DeleteTaskResponses, GetAllBoardsData, GetAllBoardsErrors, GetAllBoardsResponses, GetAllLabelsData, GetAllLabelsErrors, GetAllLabelsResponses, GetAllSettingsData, GetAllSettingsErrors, GetAllSettingsResponses, GetBoardByIdData, GetBoardByIdErrors, GetBoardByIdResponses, HelpData, HelpErrors, HelpResponses, UpdateAccessibilitySettingsData, UpdateAccessibilitySettingsErrors, UpdateAccessibilitySettingsResponses, UpdateBoardData, UpdateBoardErrors, UpdateBoardResponses, UpdateColumnData, UpdateColumnErrors, UpdateColumnResponses, UpdateColumnsOrderData, UpdateColumnsOrderErrors, UpdateColumnsOrderResponses, UpdateGeneralSettingsData, UpdateGeneralSettingsErrors, UpdateGeneralSettingsResponses, UpdateLabelData, UpdateLabelErrors, UpdateLabelResponses, UpdateLabelSettingsData, UpdateLabelSettingsErrors, UpdateLabelSettingsResponses, UpdateTaskData, UpdateTaskErrors, UpdateTaskResponses, UpdateTaskSettingsData, UpdateTaskSettingsErrors, UpdateTaskSettingsResponses, UpdateTasksOrderData, UpdateTasksOrderErrors, UpdateTasksOrderResponses } from './types.gen'; export type Options = Options2 & { /** @@ -33,6 +33,72 @@ export const help = (options: Options(options?: Options): RequestResult => (options?.client ?? client).get({ + responseType: 'json', + security: [{ scheme: 'bearer', type: 'http' }], + url: '/user/settings', + ...options +}); + +/** + * Update general settings + */ +export const updateGeneralSettings = (options: Options): RequestResult => (options.client ?? client).patch({ + responseType: 'json', + security: [{ scheme: 'bearer', type: 'http' }], + url: '/user/settings/general', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +/** + * Update task settings + */ +export const updateTaskSettings = (options: Options): RequestResult => (options.client ?? client).patch({ + responseType: 'json', + security: [{ scheme: 'bearer', type: 'http' }], + url: '/user/settings/task', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +/** + * Update label settings + */ +export const updateLabelSettings = (options: Options): RequestResult => (options.client ?? client).patch({ + responseType: 'json', + security: [{ scheme: 'bearer', type: 'http' }], + url: '/user/settings/label', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + +/** + * Update accessibility settings + */ +export const updateAccessibilitySettings = (options: Options): RequestResult => (options.client ?? client).patch({ + responseType: 'json', + security: [{ scheme: 'bearer', type: 'http' }], + url: '/user/settings/accessibility', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + /** * Get all boards */ diff --git a/src/shared/api/generated/types.gen.ts b/src/shared/api/generated/types.gen.ts index b72670411..1687cbbf5 100644 --- a/src/shared/api/generated/types.gen.ts +++ b/src/shared/api/generated/types.gen.ts @@ -113,6 +113,49 @@ export const LabelColor = { export type LabelColor = typeof LabelColor[keyof typeof LabelColor]; +export type GeneralSettings = { + id: string; + theme: 'light' | 'dark' | 'system'; + accentColor: 'blue' | 'purple' | 'red' | 'gray' | 'cyan' | 'yellow' | 'indigo' | 'green'; + firstDayOfWeek: 'monday' | 'sunday'; + dateFormat?: 'dd_mm_yyyy' | 'mm_dd_yyyy' | 'yyyy_mm_dd'; + boardBackgroundBlur: 'off' | 'low' | 'medium'; + usePointerCursors: boolean; + enableAnimations: boolean; + confirmBeforeDelete: boolean; + userId: string; +}; + +export type TaskSettings = { + id: string; + sortTasksBy: 'manual' | 'priority' | 'deadline' | 'created' | 'alphabetical'; + defaultPriority: TaskPriority; + defaultDeadlineDays: 'none' | 'today' | 'tomorrow' | 'three_days' | 'one_week'; + cardDensity: 'compact' | 'comfortable'; + showCompletedTasks: boolean; + newTaskPosition: 'top' | 'bottom'; + naturalLanguageDates: boolean; + userId: string; +}; + +export type LabelSettings = { + id: string; + showLabelsOnCard: boolean; + labelDisplay: 'full' | 'compact'; + maxLabelsShown: 'one' | 'two' | 'three' | 'all'; + userId: string; +}; + +export type AccessibilitySettings = { + id: string; + fontSize: 'small' | 'medium' | 'large'; + reducedMotion: boolean; + highContrast: boolean; + focusIndicators: boolean; + keyboardNavigationHints: boolean; + userId: string; +}; + export type ErrorResponse = { statusCode?: number; message?: string | { @@ -145,6 +188,122 @@ export type HelpResponses = { export type HelpResponse = HelpResponses[keyof HelpResponses]; +export type GetAllSettingsData = { + body?: never; + path?: never; + query?: never; + url: '/user/settings'; +}; + +export type GetAllSettingsErrors = { + 401: ErrorResponse; +}; + +export type GetAllSettingsError = GetAllSettingsErrors[keyof GetAllSettingsErrors]; + +export type GetAllSettingsResponses = { + 200: { + general?: GeneralSettings; + task?: TaskSettings; + label?: LabelSettings; + accessibility?: AccessibilitySettings; + }; +}; + +export type GetAllSettingsResponse = GetAllSettingsResponses[keyof GetAllSettingsResponses]; + +export type UpdateGeneralSettingsData = { + body: { + firstDayOfWeek: string; + boardBackgroundBlur: string; + usePointerCursors: boolean; + }; + path?: never; + query?: never; + url: '/user/settings/general'; +}; + +export type UpdateGeneralSettingsErrors = { + 401: ErrorResponse; +}; + +export type UpdateGeneralSettingsError = UpdateGeneralSettingsErrors[keyof UpdateGeneralSettingsErrors]; + +export type UpdateGeneralSettingsResponses = { + 200: GeneralSettings; +}; + +export type UpdateGeneralSettingsResponse = UpdateGeneralSettingsResponses[keyof UpdateGeneralSettingsResponses]; + +export type UpdateTaskSettingsData = { + body: { + cardDensity: string; + sortTasksBy: string; + showCompletedTasks: boolean; + }; + path?: never; + query?: never; + url: '/user/settings/task'; +}; + +export type UpdateTaskSettingsErrors = { + 401: ErrorResponse; +}; + +export type UpdateTaskSettingsError = UpdateTaskSettingsErrors[keyof UpdateTaskSettingsErrors]; + +export type UpdateTaskSettingsResponses = { + 200: TaskSettings; +}; + +export type UpdateTaskSettingsResponse = UpdateTaskSettingsResponses[keyof UpdateTaskSettingsResponses]; + +export type UpdateLabelSettingsData = { + body: { + showLabelsOnCard: boolean; + labelDisplay: string; + maxLabelsShown: string; + }; + path?: never; + query?: never; + url: '/user/settings/label'; +}; + +export type UpdateLabelSettingsErrors = { + 401: ErrorResponse; +}; + +export type UpdateLabelSettingsError = UpdateLabelSettingsErrors[keyof UpdateLabelSettingsErrors]; + +export type UpdateLabelSettingsResponses = { + 200: LabelSettings; +}; + +export type UpdateLabelSettingsResponse = UpdateLabelSettingsResponses[keyof UpdateLabelSettingsResponses]; + +export type UpdateAccessibilitySettingsData = { + body: { + fontSize: string; + reducedMotion: boolean; + highContrast: boolean; + }; + path?: never; + query?: never; + url: '/user/settings/accessibility'; +}; + +export type UpdateAccessibilitySettingsErrors = { + 401: ErrorResponse; +}; + +export type UpdateAccessibilitySettingsError = UpdateAccessibilitySettingsErrors[keyof UpdateAccessibilitySettingsErrors]; + +export type UpdateAccessibilitySettingsResponses = { + 200: AccessibilitySettings; +}; + +export type UpdateAccessibilitySettingsResponse = UpdateAccessibilitySettingsResponses[keyof UpdateAccessibilitySettingsResponses]; + export type GetAllBoardsData = { body?: never; path?: never; diff --git a/src/shared/lib/router/routeTree.gen.ts b/src/shared/lib/router/routeTree.gen.ts index de2b770c9..89e5eaecc 100644 --- a/src/shared/lib/router/routeTree.gen.ts +++ b/src/shared/lib/router/routeTree.gen.ts @@ -15,6 +15,7 @@ import { Route as DashboardIndexRouteImport } from './../../../app/routes/dashbo import { Route as DashboardBoardIdRouteImport } from './../../../app/routes/dashboard/$boardId' import { Route as AuthAuthLayoutRouteImport } from './../../../app/routes/auth/_auth-layout' import { Route as DashboardSettingsLayoutRouteImport } from './../../../app/routes/dashboard/settings/layout' +import { Route as DashboardSettingsGeneralRouteImport } from './../../../app/routes/dashboard/settings/general' import { Route as DashboardSettingsAppearanceRouteImport } from './../../../app/routes/dashboard/settings/appearance' import { Route as DashboardSettingsAccessibilityRouteImport } from './../../../app/routes/dashboard/settings/accessibility' import { Route as AuthAuthLayoutSignupRouteImport } from './../../../app/routes/auth/_auth-layout.signup' @@ -50,6 +51,12 @@ const DashboardSettingsLayoutRoute = DashboardSettingsLayoutRouteImport.update({ path: '/settings', getParentRoute: () => DashboardLayoutRoute, } as any) +const DashboardSettingsGeneralRoute = + DashboardSettingsGeneralRouteImport.update({ + id: '/general', + path: '/general', + getParentRoute: () => DashboardSettingsLayoutRoute, + } as any) const DashboardSettingsAppearanceRoute = DashboardSettingsAppearanceRouteImport.update({ id: '/appearance', @@ -84,6 +91,7 @@ export interface FileRoutesByFullPath { '/auth/signup': typeof AuthAuthLayoutSignupRoute '/dashboard/settings/accessibility': typeof DashboardSettingsAccessibilityRoute '/dashboard/settings/appearance': typeof DashboardSettingsAppearanceRoute + '/dashboard/settings/general': typeof DashboardSettingsGeneralRoute } export interface FileRoutesByTo { '/': typeof IndexRoute @@ -95,6 +103,7 @@ export interface FileRoutesByTo { '/auth/signup': typeof AuthAuthLayoutSignupRoute '/dashboard/settings/accessibility': typeof DashboardSettingsAccessibilityRoute '/dashboard/settings/appearance': typeof DashboardSettingsAppearanceRoute + '/dashboard/settings/general': typeof DashboardSettingsGeneralRoute } export interface FileRoutesById { __root__: typeof rootRouteImport @@ -108,6 +117,7 @@ export interface FileRoutesById { '/auth/_auth-layout/signup': typeof AuthAuthLayoutSignupRoute '/dashboard/settings/accessibility': typeof DashboardSettingsAccessibilityRoute '/dashboard/settings/appearance': typeof DashboardSettingsAppearanceRoute + '/dashboard/settings/general': typeof DashboardSettingsGeneralRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath @@ -122,6 +132,7 @@ export interface FileRouteTypes { | '/auth/signup' | '/dashboard/settings/accessibility' | '/dashboard/settings/appearance' + | '/dashboard/settings/general' fileRoutesByTo: FileRoutesByTo to: | '/' @@ -133,6 +144,7 @@ export interface FileRouteTypes { | '/auth/signup' | '/dashboard/settings/accessibility' | '/dashboard/settings/appearance' + | '/dashboard/settings/general' id: | '__root__' | '/' @@ -145,6 +157,7 @@ export interface FileRouteTypes { | '/auth/_auth-layout/signup' | '/dashboard/settings/accessibility' | '/dashboard/settings/appearance' + | '/dashboard/settings/general' fileRoutesById: FileRoutesById } export interface RootRouteChildren { @@ -197,6 +210,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof DashboardSettingsLayoutRouteImport parentRoute: typeof DashboardLayoutRoute } + '/dashboard/settings/general': { + id: '/dashboard/settings/general' + path: '/general' + fullPath: '/dashboard/settings/general' + preLoaderRoute: typeof DashboardSettingsGeneralRouteImport + parentRoute: typeof DashboardSettingsLayoutRoute + } '/dashboard/settings/appearance': { id: '/dashboard/settings/appearance' path: '/appearance' @@ -231,12 +251,14 @@ declare module '@tanstack/react-router' { interface DashboardSettingsLayoutRouteChildren { DashboardSettingsAccessibilityRoute: typeof DashboardSettingsAccessibilityRoute DashboardSettingsAppearanceRoute: typeof DashboardSettingsAppearanceRoute + DashboardSettingsGeneralRoute: typeof DashboardSettingsGeneralRoute } const DashboardSettingsLayoutRouteChildren: DashboardSettingsLayoutRouteChildren = { DashboardSettingsAccessibilityRoute: DashboardSettingsAccessibilityRoute, DashboardSettingsAppearanceRoute: DashboardSettingsAppearanceRoute, + DashboardSettingsGeneralRoute: DashboardSettingsGeneralRoute, } const DashboardSettingsLayoutRouteWithChildren = diff --git a/src/shared/ui/Loader.tsx b/src/shared/ui/Loader.tsx index 2523416bc..f97673a5d 100644 --- a/src/shared/ui/Loader.tsx +++ b/src/shared/ui/Loader.tsx @@ -1,13 +1,15 @@ +import type { ComponentProps } from 'react' + +import { Loader2Icon } from 'lucide-react' + import { cn } from '../lib' -export const Loader = ({ className }: { className?: string }) => ( -
    - Loading... -
    +export const Loader = ({ className, ...props }: ComponentProps<'svg'>) => ( + ) diff --git a/src/widgets/settings-wrapper/index.ts b/src/widgets/settings-wrapper/index.ts deleted file mode 100644 index b2f605722..000000000 --- a/src/widgets/settings-wrapper/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { SettingsWrapper } from './ui/SettingsWrapper' From 4245d00ed471a09f3cece0c61eaf3dc204ccf8de Mon Sep 17 00:00:00 2001 From: chertik77 Date: Fri, 17 Jul 2026 16:29:06 +0100 Subject: [PATCH 04/39] refactor: add valibot to hey open api generator --- openapi-ts.config.ts | 4 +- src/entities/board/config/bg-images.ts | 16 +- src/entities/board/config/icon.ts | 12 - src/entities/board/index.ts | 4 - src/entities/board/model/contracts.ts | 19 - src/entities/board/model/types.ts | 9 - src/entities/board/ui/FormIconSelector.tsx | 5 +- src/entities/column/@x/board.ts | 1 - src/entities/column/index.ts | 2 - src/entities/column/model/contracts.ts | 13 - src/entities/column/model/types.ts | 5 - src/entities/column/ui/Column.tsx | 4 +- src/entities/label/@x/task.ts | 1 - src/entities/label/config/color-map.ts | 6 +- src/entities/label/config/colors.ts | 10 - src/entities/label/index.ts | 3 - src/entities/label/lib/useLabelCombobox.ts | 8 +- src/entities/label/model/contracts.ts | 11 - src/entities/label/model/types.ts | 5 - src/entities/label/ui/FormLabelsCombobox.tsx | 4 +- src/entities/label/ui/LabelChip.tsx | 6 +- src/entities/settings/index.ts | 2 +- src/entities/settings/model/useSettings.ts | 9 + src/entities/task/@x/column.ts | 1 - src/entities/task/config/priority.ts | 1 - src/entities/task/index.ts | 1 - src/entities/task/lib/priority-colors.ts | 2 +- src/entities/task/model/contracts.ts | 19 +- src/entities/task/model/types.ts | 8 +- src/entities/task/ui/FormPrioritySelector.tsx | 34 +- src/entities/task/ui/Task.tsx | 6 +- src/features/board/add/model/contract.ts | 6 +- .../board/delete/api/useDeleteBoard.ts | 13 +- src/features/board/edit/model/contract.ts | 6 +- src/features/board/edit/model/types.ts | 6 +- .../column/delete/api/useDeleteColumn.ts | 17 +- .../drag-and-drop/lib/getDraggingTaskData.ts | 7 +- src/features/drag-and-drop/model/types.ts | 21 +- .../drag-and-drop/model/useDndState.ts | 27 +- src/features/label/add/model/contract.ts | 4 +- .../label/add/ui/LabelColorPicker.tsx | 5 +- .../general/api/useUpdateGeneralSettings.ts | 50 ++- .../general/config/general-settings.ts | 28 -- .../general}/lib/useThemeWithRootSync.ts | 6 +- .../settings/general/ui/GeneralSettings.tsx | 24 +- src/features/task/add/model/contract.ts | 7 +- src/features/task/delete/api/useDeleteTask.ts | 13 +- src/features/task/edit/model/contract.ts | 7 +- src/features/task/edit/model/types.ts | 7 +- src/features/task/edit/ui/EditTaskForm.tsx | 2 +- .../task/filter/lib/useFilteredTasks.ts | 6 +- .../task/filter/ui/PriorityFilter.tsx | 5 +- .../change-theme/lib/useMetaThemeColor.ts | 9 +- .../user/change-theme/ui/ThemeSelect.tsx | 2 +- src/shared/api/generated/sdk.gen.ts | 155 ++++++- src/shared/api/generated/transformers.gen.ts | 110 ----- src/shared/api/generated/types.gen.ts | 53 +-- src/shared/api/generated/valibot.gen.ts | 412 ++++++++++++++++++ src/shared/api/index.ts | 1 + src/shared/lib/form/useAppForm.ts | 4 +- src/shared/lib/index.ts | 1 + .../lib/object-keys/typed-object-keys.ts | 3 + .../kanban-board/api/useCompleteTask.ts | 13 +- .../ui/column-list/ColumnListItem.tsx | 4 +- .../ui/column-list/MemoizedColumn.tsx | 4 +- .../ui/task-list/MemoizedTask.tsx | 4 +- .../kanban-board/ui/task-list/TaskList.tsx | 4 +- .../ui/task-list/TaskListItem.tsx | 4 +- .../sidebar/ui/SidebarBoardListItem.tsx | 4 +- 69 files changed, 816 insertions(+), 469 deletions(-) delete mode 100644 src/entities/board/config/icon.ts delete mode 100644 src/entities/board/model/contracts.ts delete mode 100644 src/entities/board/model/types.ts delete mode 100644 src/entities/column/@x/board.ts delete mode 100644 src/entities/column/model/contracts.ts delete mode 100644 src/entities/column/model/types.ts delete mode 100644 src/entities/label/model/contracts.ts delete mode 100644 src/entities/label/model/types.ts create mode 100644 src/entities/settings/model/useSettings.ts delete mode 100644 src/entities/task/@x/column.ts delete mode 100644 src/entities/task/config/priority.ts delete mode 100644 src/features/settings/general/config/general-settings.ts rename src/features/{user/change-theme => settings/general}/lib/useThemeWithRootSync.ts (84%) delete mode 100644 src/shared/api/generated/transformers.gen.ts create mode 100644 src/shared/api/generated/valibot.gen.ts create mode 100644 src/shared/lib/object-keys/typed-object-keys.ts diff --git a/openapi-ts.config.ts b/openapi-ts.config.ts index 28dd9abd1..3d375f3e6 100644 --- a/openapi-ts.config.ts +++ b/openapi-ts.config.ts @@ -8,13 +8,13 @@ export default defineConfig({ input: `${process.env.VITE_API_BASE_URL}/openapi.json`, output: 'src/shared/api/generated', plugins: [ + 'valibot', '@tanstack/react-query', { name: '@hey-api/client-axios', runtimeConfigPath: 'src/shared/lib/openapi/hey-api.ts' }, { enums: 'javascript', name: '@hey-api/typescript' }, - { name: '@hey-api/sdk', transformer: true }, - { name: '@hey-api/transformers', dates: true } + { name: '@hey-api/sdk', validator: true } ] }) diff --git a/src/entities/board/config/bg-images.ts b/src/entities/board/config/bg-images.ts index f5a1dd3a2..9d30dea6e 100644 --- a/src/entities/board/config/bg-images.ts +++ b/src/entities/board/config/bg-images.ts @@ -1,4 +1,16 @@ -export const BOARD_BG_IMAGES = [ +import type { BoardBackgroundId } from '@/shared/api' + +type BoardBackground = { + id: BoardBackgroundId + icon: + | string + | { + light: string + dark: string + } +} + +export const BOARD_BG_IMAGES: BoardBackground[] = [ { id: 'default', icon: { @@ -68,5 +80,3 @@ export const BOARD_BG_IMAGES = [ icon: 'https://res.cloudinary.com/dmbnnewoy/image/upload/v1707102068/TaskPro/board_bg_images/bg_icons/northern_lights.png' } ] as const - -export const BOARD_BG_IMAGES_IDS = BOARD_BG_IMAGES.map(i => i.id) diff --git a/src/entities/board/config/icon.ts b/src/entities/board/config/icon.ts deleted file mode 100644 index a7d83d02f..000000000 --- a/src/entities/board/config/icon.ts +++ /dev/null @@ -1,12 +0,0 @@ -export const BOARD_ICONS = [ - 'sparkles', - 'layout', - 'kanban', - 'layers', - 'compass', - 'target', - 'sunrise', - 'orbit', - 'palette', - 'leaf' -] as const diff --git a/src/entities/board/index.ts b/src/entities/board/index.ts index 44ea69609..802905bd5 100644 --- a/src/entities/board/index.ts +++ b/src/entities/board/index.ts @@ -1,9 +1,5 @@ -export * as BoardContracts from './model/contracts' -export * as BoardTypes from './model/types' export { useGetParamBoardId } from './lib/useGetParamBoardId' export { WHITE_TEXT_BOARD_BG_IDS } from './config/white-text-board-bg-ids' -export { BOARD_BG_IMAGES_IDS } from './config/bg-images' -export { BOARD_ICONS } from './config/icon' export { FormBgImageSelector } from './ui/FormBgImageSelector' export { BoardErrorView } from './ui/BoardErrorView' export { FormIconSelector } from './ui/FormIconSelector' diff --git a/src/entities/board/model/contracts.ts b/src/entities/board/model/contracts.ts deleted file mode 100644 index 479b53689..000000000 --- a/src/entities/board/model/contracts.ts +++ /dev/null @@ -1,19 +0,0 @@ -import * as v from 'valibot' - -import { ColumnSchema } from '@/entities/column/@x/board' - -import { BOARD_BG_IMAGES_IDS } from '../config/bg-images' -import { BOARD_ICONS } from '../config/icon' - -export const BoardSchema = v.object({ - id: v.string(), - title: v.string(), - icon: v.picklist(BOARD_ICONS), - background: v.object({ - identifier: v.picklist(BOARD_BG_IMAGES_IDS), - url: v.nullable(v.string()) - }), - columns: v.array(ColumnSchema) -}) - -export const BoardsSchema = v.array(v.omit(BoardSchema, ['columns'])) diff --git a/src/entities/board/model/types.ts b/src/entities/board/model/types.ts deleted file mode 100644 index fa12ab5ae..000000000 --- a/src/entities/board/model/types.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type { InferOutput } from 'valibot' -import type { BOARD_BG_IMAGES_IDS } from '../config/bg-images' -import type { BOARD_ICONS } from '../config/icon' -import type { BoardSchema, BoardsSchema } from './contracts' - -export type BoardSchema = InferOutput -export type BoardsSchema = InferOutput -export type BoardBgImageId = (typeof BOARD_BG_IMAGES_IDS)[number] -export type BoardIcon = (typeof BOARD_ICONS)[number] diff --git a/src/entities/board/ui/FormIconSelector.tsx b/src/entities/board/ui/FormIconSelector.tsx index 593b89935..43841c847 100644 --- a/src/entities/board/ui/FormIconSelector.tsx +++ b/src/entities/board/ui/FormIconSelector.tsx @@ -2,10 +2,9 @@ import { Radio } from '@base-ui/react/radio' import { RadioGroup } from '@base-ui/react/radio-group' import { DynamicIcon } from 'lucide-react/dynamic' +import { BoardIcon } from '@/shared/api' import { FormItem, useFormField } from '@/shared/ui' -import { BOARD_ICONS } from '../config/icon' - export const FormIconSelector = () => { const { field: { value, onChange } @@ -16,7 +15,7 @@ export const FormIconSelector = () => { className='flex items-center gap-2' value={value} onValueChange={onChange}> - {BOARD_ICONS.map(icon => ( + {Object.values(BoardIcon).map(icon => ( -export type ColumnsSchema = InferOutput diff --git a/src/entities/column/ui/Column.tsx b/src/entities/column/ui/Column.tsx index 2892c7522..0209cb6f3 100644 --- a/src/entities/column/ui/Column.tsx +++ b/src/entities/column/ui/Column.tsx @@ -1,5 +1,5 @@ +import type { Column as TColumn } from '@/shared/api' import type { ComponentProps, ReactNode } from 'react' -import type { ColumnSchema } from '../model/types' import { createContext, use, useMemo } from 'react' import { mergeProps, useRender } from '@base-ui/react' @@ -9,7 +9,7 @@ import { GripVerticalIcon } from 'lucide-react' import { cn, useTabletAndBelowMediaQuery } from '@/shared/lib' type ColumnContext = { - column: ColumnSchema + column: TColumn } const ColumnContext = createContext(undefined) diff --git a/src/entities/label/@x/task.ts b/src/entities/label/@x/task.ts index 9ad249e4e..506230308 100644 --- a/src/entities/label/@x/task.ts +++ b/src/entities/label/@x/task.ts @@ -1,3 +1,2 @@ -export { LabelSchema } from '../model/contracts' export { LABEL_COLOR_MAP } from '../config/color-map' export { Label } from '../ui/Label' diff --git a/src/entities/label/config/color-map.ts b/src/entities/label/config/color-map.ts index a838a12ac..ef7934410 100644 --- a/src/entities/label/config/color-map.ts +++ b/src/entities/label/config/color-map.ts @@ -1,6 +1,6 @@ -import type { LABEL_COLORS } from './colors' +import type { LabelColor } from '@/shared/api' -export const LABEL_COLOR_MAP: Record<(typeof LABEL_COLORS)[number], string> = { +export const LABEL_COLOR_MAP: Record = { blue: 'bg-blue-100 text-blue-700 dark:bg-blue-500/15 dark:text-blue-300', purple: 'bg-violet-100 text-violet-700 dark:bg-violet-500/15 dark:text-violet-300', @@ -15,7 +15,7 @@ export const LABEL_COLOR_MAP: Record<(typeof LABEL_COLORS)[number], string> = { gray: 'bg-gray-100 text-gray-700 dark:bg-gray-500/15 dark:text-gray-300' } as const -export const COLOR_MAP: Record<(typeof LABEL_COLORS)[number], string> = { +export const COLOR_MAP: Record = { blue: 'bg-blue-500', purple: 'bg-violet-500', green: 'bg-emerald-500', diff --git a/src/entities/label/config/colors.ts b/src/entities/label/config/colors.ts index 455e06b43..e69de29bb 100644 --- a/src/entities/label/config/colors.ts +++ b/src/entities/label/config/colors.ts @@ -1,10 +0,0 @@ -export const LABEL_COLORS = [ - 'blue', - 'purple', - 'red', - 'gray', - 'cyan', - 'yellow', - 'green', - 'indigo' -] as const diff --git a/src/entities/label/index.ts b/src/entities/label/index.ts index a7c0789f6..0cc50a125 100644 --- a/src/entities/label/index.ts +++ b/src/entities/label/index.ts @@ -1,8 +1,5 @@ -export * as LabelTypes from './model/types' -export * as LabelContracts from './model/contracts' export { FormLabelsCombobox } from './ui/FormLabelsCombobox' export { COLOR_MAP, LABEL_COLOR_MAP } from './config/color-map' -export { LABEL_COLORS } from './config/colors' export { useLabelCombobox } from './lib/useLabelCombobox' export { useLabelModalStore } from './model/modal.store' export { Label } from './ui/Label' diff --git a/src/entities/label/lib/useLabelCombobox.ts b/src/entities/label/lib/useLabelCombobox.ts index c5c913aa7..6507f4ead 100644 --- a/src/entities/label/lib/useLabelCombobox.ts +++ b/src/entities/label/lib/useLabelCombobox.ts @@ -1,4 +1,4 @@ -import type { LabelSchema } from '../model/types' +import type { Label } from '@/shared/api' import { useMemo, useState } from 'react' import { useQuery } from '@tanstack/react-query' @@ -11,7 +11,9 @@ const CREATE_SENTINEL = '__create' const isCreateOption = (id: string) => id === CREATE_SENTINEL -const buildCreateOption = (name: string): LabelSchema => ({ +const buildCreateOption = ( + name: string +): Pick => ({ id: CREATE_SENTINEL, name: `Create "${name}"`, color: 'gray' @@ -50,7 +52,7 @@ export const useLabelCombobox = (labelsValues: string[] | undefined) => { isOpen: true, props: { name: inputValue, - onCreated: (newLabel: LabelSchema) => { + onCreated: (newLabel: Label) => { const current = labelsValues ?? [] onChange([...current, newLabel.id]) } diff --git a/src/entities/label/model/contracts.ts b/src/entities/label/model/contracts.ts deleted file mode 100644 index 990875e63..000000000 --- a/src/entities/label/model/contracts.ts +++ /dev/null @@ -1,11 +0,0 @@ -import * as v from 'valibot' - -import { LABEL_COLORS } from '../config/colors' - -export const LabelSchema = v.object({ - id: v.string(), - name: v.string(), - color: v.picklist(LABEL_COLORS) -}) - -export const LabelsSchema = v.array(LabelSchema) diff --git a/src/entities/label/model/types.ts b/src/entities/label/model/types.ts deleted file mode 100644 index f59b7ceb4..000000000 --- a/src/entities/label/model/types.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { InferOutput } from 'valibot' -import type { LabelSchema, LabelsSchema } from './contracts' - -export type LabelSchema = InferOutput -export type LabelsSchema = InferOutput diff --git a/src/entities/label/ui/FormLabelsCombobox.tsx b/src/entities/label/ui/FormLabelsCombobox.tsx index b98d9f217..3f9598faa 100644 --- a/src/entities/label/ui/FormLabelsCombobox.tsx +++ b/src/entities/label/ui/FormLabelsCombobox.tsx @@ -1,4 +1,4 @@ -import type { LabelSchema } from '../model/types' +import type { Label } from '@/shared/api' import { cn } from '@/shared/lib' import { @@ -12,7 +12,7 @@ import { COLOR_MAP } from '../config/color-map' import { useLabelCombobox } from '../lib/useLabelCombobox' import { LabelChip } from './LabelChip' -const Combobox = createTypeSafeCombobox() +const Combobox = createTypeSafeCombobox() type FormLabelsComboboxProps = { labelsValues: string[] | undefined diff --git a/src/entities/label/ui/LabelChip.tsx b/src/entities/label/ui/LabelChip.tsx index 1d4631df5..a0a680522 100644 --- a/src/entities/label/ui/LabelChip.tsx +++ b/src/entities/label/ui/LabelChip.tsx @@ -1,13 +1,13 @@ -import type { LabelSchema } from '../model/types' +import type { Label } from '@/shared/api' import { createTypeSafeCombobox } from '@/shared/ui' import { LABEL_COLOR_MAP } from '../config/color-map' -const Combobox = createTypeSafeCombobox() +const Combobox = createTypeSafeCombobox() type LabelChipProps = { - labelMap: Map + labelMap: Map values: string[] } diff --git a/src/entities/settings/index.ts b/src/entities/settings/index.ts index 562b5a987..94db8ba4d 100644 --- a/src/entities/settings/index.ts +++ b/src/entities/settings/index.ts @@ -1,2 +1,2 @@ export { Settings } from './ui/Settings' -export { SwitchControl } from './ui/controls/SwitchControl' +export { useSettings } from './model/useSettings' diff --git a/src/entities/settings/model/useSettings.ts b/src/entities/settings/model/useSettings.ts new file mode 100644 index 000000000..2eb91dc0e --- /dev/null +++ b/src/entities/settings/model/useSettings.ts @@ -0,0 +1,9 @@ +import { useQuery } from '@tanstack/react-query' + +import { getAllSettingsOptions } from '@/shared/api' + +export const useSettings = () => { + const { data: settings } = useQuery(getAllSettingsOptions()) + + return settings +} diff --git a/src/entities/task/@x/column.ts b/src/entities/task/@x/column.ts deleted file mode 100644 index d7b08fbed..000000000 --- a/src/entities/task/@x/column.ts +++ /dev/null @@ -1 +0,0 @@ -export { TaskSchema } from '../model/contracts' diff --git a/src/entities/task/config/priority.ts b/src/entities/task/config/priority.ts deleted file mode 100644 index cf7387307..000000000 --- a/src/entities/task/config/priority.ts +++ /dev/null @@ -1 +0,0 @@ -export const TASK_PRIORITIES = ['high', 'medium', 'low', 'without'] as const diff --git a/src/entities/task/index.ts b/src/entities/task/index.ts index ec0df84c4..9b82b7ac8 100644 --- a/src/entities/task/index.ts +++ b/src/entities/task/index.ts @@ -1,6 +1,5 @@ export * as TaskTypes from './model/types' export * as TaskContracts from './model/contracts' -export { TASK_PRIORITIES } from './config/priority' export { TASK_DEADLINES } from './config/deadline' export { getTaskPriorityColor } from './lib/priority-colors' export { formatDeadlineDate } from './lib/format-deadline-date' diff --git a/src/entities/task/lib/priority-colors.ts b/src/entities/task/lib/priority-colors.ts index 0fc4cbcb8..2b1111a52 100644 --- a/src/entities/task/lib/priority-colors.ts +++ b/src/entities/task/lib/priority-colors.ts @@ -1,4 +1,4 @@ -import type { TaskPriority } from '../model/types' +import type { TaskPriority } from '@/shared/api' const taskPriorityColors: Record = { low: 'bg-blue', diff --git a/src/entities/task/model/contracts.ts b/src/entities/task/model/contracts.ts index 431cf2fa1..beeba7ae0 100644 --- a/src/entities/task/model/contracts.ts +++ b/src/entities/task/model/contracts.ts @@ -1,26 +1,11 @@ import * as v from 'valibot' -import { LabelSchema } from '@/entities/label/@x/task' +import { vTaskPriority } from '@/shared/api' import { TASK_DEADLINES } from '../config/deadline' -import { TASK_PRIORITIES } from '../config/priority' - -export const TaskSchema = v.object({ - id: v.string(), - title: v.string(), - order: v.number(), - completed: v.boolean(), - columnId: v.string(), - description: v.nullable(v.string()), - priority: v.picklist(TASK_PRIORITIES), - labels: v.optional(v.array(LabelSchema)), - deadline: v.nullable(v.date()) -}) - -export const TasksSchema = v.array(TaskSchema) export const TaskSearchSchema = v.object({ - priority: v.optional(v.picklist(TASK_PRIORITIES)), + priority: v.optional(vTaskPriority), deadline: v.optional(v.picklist(TASK_DEADLINES)), search: v.optional(v.pipe(v.string(), v.trim()), '') }) diff --git a/src/entities/task/model/types.ts b/src/entities/task/model/types.ts index 5f82c20d4..cf7bcbe1f 100644 --- a/src/entities/task/model/types.ts +++ b/src/entities/task/model/types.ts @@ -1,10 +1,4 @@ import type { InferOutput } from 'valibot' -import type { TASK_DEADLINES } from '../config/deadline' -import type { TASK_PRIORITIES } from '../config/priority' -import type { TaskSchema, TaskSearchSchema, TasksSchema } from './contracts' +import type { TaskSearchSchema } from './contracts' -export type TaskSchema = InferOutput -export type TasksSchema = InferOutput export type TaskSearchSchema = InferOutput -export type TaskDeadline = (typeof TASK_DEADLINES)[number] -export type TaskPriority = (typeof TASK_PRIORITIES)[number] diff --git a/src/entities/task/ui/FormPrioritySelector.tsx b/src/entities/task/ui/FormPrioritySelector.tsx index 653403da9..14abff3d9 100644 --- a/src/entities/task/ui/FormPrioritySelector.tsx +++ b/src/entities/task/ui/FormPrioritySelector.tsx @@ -1,7 +1,7 @@ +import { TaskPriority } from '@/shared/api' import { capitalize } from '@/shared/lib' import { FormItem, RadioGroup, RadioGroupItem, useFormField } from '@/shared/ui' -import { TASK_PRIORITIES } from '../config/priority' import { getTaskPriorityColor } from '../lib/priority-colors' export const FormPrioritySelector = () => { @@ -14,21 +14,23 @@ export const FormPrioritySelector = () => { value={value} className='gap-3' onValueChange={onChange}> - {TASK_PRIORITIES.toReversed().map(priority => ( - - - - ))} + {Object.values(TaskPriority) + .toReversed() + .map(priority => ( + + + + ))} ) } diff --git a/src/entities/task/ui/Task.tsx b/src/entities/task/ui/Task.tsx index cf361e33d..1199df2d6 100644 --- a/src/entities/task/ui/Task.tsx +++ b/src/entities/task/ui/Task.tsx @@ -1,5 +1,5 @@ +import type { Task as TTask } from '@/shared/api' import type { ComponentProps } from 'react' -import type { TaskSchema } from '../model/types' import { createContext, use, useMemo } from 'react' import { mergeProps, useRender } from '@base-ui/react' @@ -15,7 +15,7 @@ import { formatDeadlineDate } from '../lib/format-deadline-date' import { getTaskPriorityColor } from '../lib/priority-colors' type TaskContext = { - task: TaskSchema + task: TTask } const TaskContext = createContext(undefined) @@ -195,7 +195,7 @@ const TaskDeadline = ({ className }: { className?: string }) => {

    Deadline

    -

    {formatDeadlineDate(task.deadline)}

    +

    {formatDeadlineDate(new Date(task.deadline))}

) ) diff --git a/src/features/board/add/model/contract.ts b/src/features/board/add/model/contract.ts index 6d517ea64..1bdf57e17 100644 --- a/src/features/board/add/model/contract.ts +++ b/src/features/board/add/model/contract.ts @@ -1,6 +1,6 @@ import * as v from 'valibot' -import { BOARD_BG_IMAGES_IDS, BOARD_ICONS } from '@/entities/board' +import { vBoardBackgroundId, vBoardIcon } from '@/shared/api' export const AddBoardSchema = v.object({ title: v.pipe( @@ -8,8 +8,8 @@ export const AddBoardSchema = v.object({ v.trim(), v.minLength(3, 'Please enter at least 3 characters.') ), - icon: v.fallback(v.picklist(BOARD_ICONS), 'sparkles'), - background: v.fallback(v.picklist(BOARD_BG_IMAGES_IDS), 'default') + icon: v.fallback(vBoardIcon, 'sparkles'), + background: v.fallback(vBoardBackgroundId, 'default') }) export type AddBoardSchema = v.InferOutput diff --git a/src/features/board/delete/api/useDeleteBoard.ts b/src/features/board/delete/api/useDeleteBoard.ts index 51c1e353b..581e36c27 100644 --- a/src/features/board/delete/api/useDeleteBoard.ts +++ b/src/features/board/delete/api/useDeleteBoard.ts @@ -1,10 +1,10 @@ import { useMutation, useQueryClient } from '@tanstack/react-query' import { useNavigate } from '@tanstack/react-router' -import { parse } from 'valibot' +import { array, parse } from 'valibot' -import { BoardContracts, useGetParamBoardId } from '@/entities/board' +import { useGetParamBoardId } from '@/entities/board' -import { deleteBoard, getAllBoardsQueryKey } from '@/shared/api' +import { deleteBoard, getAllBoardsQueryKey, vBoard } from '@/shared/api' export const useDeleteBoard = () => { const queryClient = useQueryClient() @@ -26,15 +26,12 @@ export const useDeleteBoard = () => { const previousBoards = queryClient.getQueryData(allBoardsQueryKey) - const parsedPreviousBoards = parse( - BoardContracts.BoardsSchema, - previousBoards - ) + const parsedPreviousBoards = parse(array(vBoard), previousBoards) queryClient.setQueryData(allBoardsQueryKey, oldBoards => { if (!oldBoards) return oldBoards - const parsedOldBoards = parse(BoardContracts.BoardsSchema, oldBoards) + const parsedOldBoards = parse(array(vBoard), oldBoards) return parsedOldBoards.filter(b => b.id !== boardId) }) diff --git a/src/features/board/edit/model/contract.ts b/src/features/board/edit/model/contract.ts index b46e4ea74..06f242350 100644 --- a/src/features/board/edit/model/contract.ts +++ b/src/features/board/edit/model/contract.ts @@ -1,6 +1,6 @@ import * as v from 'valibot' -import { BOARD_BG_IMAGES_IDS, BOARD_ICONS } from '@/entities/board' +import { vBoardBackgroundId, vBoardIcon } from '@/shared/api' export const EditBoardSchema = v.partial( v.object({ @@ -9,8 +9,8 @@ export const EditBoardSchema = v.partial( v.trim(), v.minLength(3, 'Please enter at least 3 characters.') ), - icon: v.picklist(BOARD_ICONS), - background: v.picklist(BOARD_BG_IMAGES_IDS) + icon: vBoardIcon, + background: vBoardBackgroundId }) ) diff --git a/src/features/board/edit/model/types.ts b/src/features/board/edit/model/types.ts index 687902de8..d4365e5fd 100644 --- a/src/features/board/edit/model/types.ts +++ b/src/features/board/edit/model/types.ts @@ -1,7 +1,7 @@ -import type { BoardTypes } from '@/entities/board' +import type { BoardBackgroundId, BoardIcon } from '@/shared/api' export type EditBoardData = { title: string - icon: BoardTypes.BoardIcon - background: BoardTypes.BoardBgImageId + icon: BoardIcon + background: BoardBackgroundId } diff --git a/src/features/column/delete/api/useDeleteColumn.ts b/src/features/column/delete/api/useDeleteColumn.ts index 993ce51cb..e9cb016d3 100644 --- a/src/features/column/delete/api/useDeleteColumn.ts +++ b/src/features/column/delete/api/useDeleteColumn.ts @@ -1,9 +1,13 @@ import { useMutation, useQueryClient } from '@tanstack/react-query' import { parse } from 'valibot' -import { BoardContracts, useGetParamBoardId } from '@/entities/board' +import { useGetParamBoardId } from '@/entities/board' -import { deleteColumnMutation, getBoardByIdQueryKey } from '@/shared/api' +import { + deleteColumnMutation, + getBoardByIdQueryKey, + vBoard +} from '@/shared/api' export const useDeleteColumn = () => { const queryClient = useQueryClient() @@ -23,19 +27,16 @@ export const useDeleteColumn = () => { const previousBoard = queryClient.getQueryData(boardQueryKey) - const parsedPreviousBoard = parse( - BoardContracts.BoardSchema, - previousBoard - ) + const parsedPreviousBoard = parse(vBoard, previousBoard) queryClient.setQueryData(boardQueryKey, oldBoard => { if (!oldBoard) return oldBoard - const parsedOldBoard = parse(BoardContracts.BoardSchema, oldBoard) + const parsedOldBoard = parse(vBoard, oldBoard) return { ...parsedOldBoard, - columns: parsedOldBoard.columns.filter( + columns: parsedOldBoard.columns?.filter( column => column.id !== columnId ) } diff --git a/src/features/drag-and-drop/lib/getDraggingTaskData.ts b/src/features/drag-and-drop/lib/getDraggingTaskData.ts index 5b5d32c2d..f816f6272 100644 --- a/src/features/drag-and-drop/lib/getDraggingTaskData.ts +++ b/src/features/drag-and-drop/lib/getDraggingTaskData.ts @@ -1,12 +1,11 @@ -import type { ColumnTypes } from '@/entities/column' -import type { TaskTypes } from '@/entities/task' +import type { Column, Task } from '@/shared/api' import type { UniqueIdentifier } from '@dnd-kit/core' export const getDraggingTaskData = ( taskId: UniqueIdentifier, columnId: string | null, - columns: ColumnTypes.ColumnsSchema, - tasks: TaskTypes.TasksSchema + columns: Column[], + tasks: Task[] ) => { const columnTasks = tasks.filter(c => c.columnId === columnId) const taskPosition = columnTasks.findIndex(c => c.id === taskId) diff --git a/src/features/drag-and-drop/model/types.ts b/src/features/drag-and-drop/model/types.ts index f147ca4eb..90135d19b 100644 --- a/src/features/drag-and-drop/model/types.ts +++ b/src/features/drag-and-drop/model/types.ts @@ -1,25 +1,24 @@ -import type { ColumnTypes } from '@/entities/column' -import type { TaskTypes } from '@/entities/task' +import type { Column, Task } from '@/shared/api' import type { Dispatch, ReactNode, SetStateAction } from 'react' export type DragAndDropContext = { - columns: ColumnTypes.ColumnsSchema - tasks: TaskTypes.TasksSchema - activeColumn: ColumnTypes.ColumnSchema | null - activeTask: TaskTypes.TaskSchema | null + columns: Column[] + tasks: Task[] + activeColumn: Column | null + activeTask: Task | null } export type DragAndDropProviderProps = { children: ReactNode - initialColumns: ColumnTypes.ColumnsSchema | undefined + initialColumns: Column[] | undefined } export type TaskDragHandlersProps = Pick & { - setTasks: Dispatch> - setActiveTask: Dispatch> + setTasks: Dispatch> + setActiveTask: Dispatch> } export type ColumnDragHandlersProps = Pick & { - setColumns: Dispatch> - setActiveColumn: Dispatch> + setColumns: Dispatch> + setActiveColumn: Dispatch> } diff --git a/src/features/drag-and-drop/model/useDndState.ts b/src/features/drag-and-drop/model/useDndState.ts index 09ca0df98..11201d36a 100644 --- a/src/features/drag-and-drop/model/useDndState.ts +++ b/src/features/drag-and-drop/model/useDndState.ts @@ -1,20 +1,16 @@ -import type { ColumnTypes } from '@/entities/column' -import type { TaskTypes } from '@/entities/task' +import type { Column, Task } from '@/shared/api' import { useMemo, useState } from 'react' -import { parse } from 'valibot' +import { array, parse } from 'valibot' -import { ColumnContracts } from '@/entities/column' -import { TaskContracts } from '@/entities/task' +import { vColumn, vTask } from '@/shared/api' -export const useDndState = ( - initialColumns: ColumnTypes.ColumnsSchema | undefined -) => { +export const useDndState = (initialColumns: Column[] | undefined) => { const [parsedColumns, parsedTasks] = useMemo( () => [ - parse(ColumnContracts.ColumnsSchema, initialColumns), + parse(array(vColumn), initialColumns), parse( - TaskContracts.TasksSchema, + array(vTask), initialColumns?.flatMap(c => c.tasks) ) ], @@ -25,19 +21,16 @@ export const useDndState = ( const [tasks, setTasks] = useState(parsedTasks) const [prevInitialColumns, setPrevInitialColumns] = useState(initialColumns) - const [activeTask, setActiveTask] = useState( - null - ) + const [activeTask, setActiveTask] = useState(null) - const [activeColumn, setActiveColumn] = - useState(null) + const [activeColumn, setActiveColumn] = useState(null) if (initialColumns !== prevInitialColumns) { setPrevInitialColumns(initialColumns) - const parsedColumns = parse(ColumnContracts.ColumnsSchema, initialColumns) + const parsedColumns = parse(array(vColumn), initialColumns) const parsedTasks = parse( - TaskContracts.TasksSchema, + array(vTask), parsedColumns.flatMap(c => c.tasks) ) diff --git a/src/features/label/add/model/contract.ts b/src/features/label/add/model/contract.ts index e388ad000..a2a317514 100644 --- a/src/features/label/add/model/contract.ts +++ b/src/features/label/add/model/contract.ts @@ -1,6 +1,6 @@ import * as v from 'valibot' -import { LABEL_COLORS } from '@/entities/label' +import { vLabelColor } from '@/shared/api' export const AddLabelSchema = v.object({ name: v.pipe( @@ -8,7 +8,7 @@ export const AddLabelSchema = v.object({ v.trim(), v.minLength(2, 'Please enter at least 2 characters.') ), - color: v.fallback(v.picklist(LABEL_COLORS), 'blue') + color: v.fallback(vLabelColor, 'blue') }) export type AddLabelSchema = v.InferOutput diff --git a/src/features/label/add/ui/LabelColorPicker.tsx b/src/features/label/add/ui/LabelColorPicker.tsx index 5fd0778cc..98701e2a6 100644 --- a/src/features/label/add/ui/LabelColorPicker.tsx +++ b/src/features/label/add/ui/LabelColorPicker.tsx @@ -1,8 +1,9 @@ import { Radio } from '@base-ui/react/radio' import { RadioGroup } from '@base-ui/react/radio-group' -import { COLOR_MAP, LABEL_COLORS } from '@/entities/label' +import { COLOR_MAP } from '@/entities/label' +import { LabelColor } from '@/shared/api' import { cn } from '@/shared/lib' import { FormItem, useFormField } from '@/shared/ui' @@ -16,7 +17,7 @@ export const LabelColorPicker = () => { className='flex items-center gap-2' value={value} onValueChange={onChange}> - {LABEL_COLORS.map(color => ( + {Object.values(LabelColor).map(color => ( - useMutation(updateGeneralSettingsMutation()) +export const useUpdateGeneralSettings = () => { + const queryClient = useQueryClient() + + return useMutation({ + ...updateGeneralSettingsMutation(), + meta: { + errorMessage: 'We couldn’t update your settings. Please try again' + }, + onMutate: async ({ body }) => { + await queryClient.cancelQueries({ queryKey: getAllSettingsQueryKey() }) + + const previousSettings = queryClient.getQueryData( + getAllSettingsQueryKey() + ) + + queryClient.setQueryData( + getAllSettingsOptions().queryKey, + oldSettings => { + if (!oldSettings?.general) return oldSettings + + return { + ...oldSettings, + general: { ...oldSettings.general, ...body } + } + } + ) + + return { previousSettings } + }, + onError: (_, __, context) => { + queryClient.setQueryData( + getAllSettingsQueryKey(), + context?.previousSettings + ) + }, + onSettled: () => { + queryClient.invalidateQueries({ queryKey: getAllSettingsQueryKey() }) + } + }) +} diff --git a/src/features/settings/general/config/general-settings.ts b/src/features/settings/general/config/general-settings.ts deleted file mode 100644 index 09da99192..000000000 --- a/src/features/settings/general/config/general-settings.ts +++ /dev/null @@ -1,28 +0,0 @@ -import type { SettingsTypes } from '@/entities/settings' - -import { SelectControl } from '@/entities/settings' - -export const GENERAl_SETTINGS: SettingsTypes.Setting[] = [ - { - key: 'theme', - title: 'Theme', - description: 'Choose between light, dark, or system appearance.', - Control: SelectControl, - controlProps: { - options: [ - { - label: 'Small', - value: 'small' - }, - { - label: 'Medium', - value: 'medium' - }, - { - label: 'Large', - value: 'large' - } - ] - } - } -] as const diff --git a/src/features/user/change-theme/lib/useThemeWithRootSync.ts b/src/features/settings/general/lib/useThemeWithRootSync.ts similarity index 84% rename from src/features/user/change-theme/lib/useThemeWithRootSync.ts rename to src/features/settings/general/lib/useThemeWithRootSync.ts index 8ad9ed22c..566313540 100644 --- a/src/features/user/change-theme/lib/useThemeWithRootSync.ts +++ b/src/features/settings/general/lib/useThemeWithRootSync.ts @@ -1,12 +1,12 @@ import { useEffect } from 'react' -import { useMe } from '@/entities/user' +import { useSettings } from '@/entities/settings' import { DEFAULT_THEME, resolveTheme } from '@/shared/config' export const useThemeWithRootSync = () => { - const user = useMe() - const theme = user?.theme ?? DEFAULT_THEME + const settings = useSettings() + const theme = settings?.general?.theme ?? DEFAULT_THEME useEffect(() => { const root = window.document.documentElement diff --git a/src/features/settings/general/ui/GeneralSettings.tsx b/src/features/settings/general/ui/GeneralSettings.tsx index 58bb18e33..9abdfa36b 100644 --- a/src/features/settings/general/ui/GeneralSettings.tsx +++ b/src/features/settings/general/ui/GeneralSettings.tsx @@ -11,17 +11,21 @@ import { SelectItem, SelectItemText, SelectTrigger, - SelectValue + SelectValue, + Switch } from '@/shared/ui' import { useGeneralSettings } from '../api/useGeneralSettings' import { useUpdateGeneralSettings } from '../api/useUpdateGeneralSettings' +import { useThemeWithRootSync } from '../lib/useThemeWithRootSync' export const GeneralSettings = () => { const { data, isPending } = useGeneralSettings() const { mutate: update } = useUpdateGeneralSettings() + const { theme: currentTheme } = useThemeWithRootSync() + return ( { + + + Pointer cursors + + Show pointer cursors when hovering interactive elements. + + + + update({ body: { usePointerCursors: v } })} + /> + + ) } diff --git a/src/features/task/add/model/contract.ts b/src/features/task/add/model/contract.ts index e795ee86d..f4ec72245 100644 --- a/src/features/task/add/model/contract.ts +++ b/src/features/task/add/model/contract.ts @@ -1,7 +1,7 @@ import { startOfDay } from 'date-fns' import * as v from 'valibot' -import { TASK_PRIORITIES } from '@/entities/task' +import { vTaskPriority } from '@/shared/api' export const AddTaskSchema = v.object({ title: v.pipe( @@ -20,7 +20,7 @@ export const AddTaskSchema = v.object({ v.minLength(3, 'Please enter at least 3 characters.') ) ]), - priority: v.fallback(v.picklist(TASK_PRIORITIES), 'without'), + priority: v.fallback(vTaskPriority, 'without'), labels: v.optional(v.array(v.string())), deadline: v.optional( v.pipe( @@ -28,7 +28,8 @@ export const AddTaskSchema = v.object({ v.check( d => startOfDay(d) >= startOfDay(new Date()), 'Deadline must be today or in the future.' - ) + ), + v.transform(v => v.toISOString()) ) ) }) diff --git a/src/features/task/delete/api/useDeleteTask.ts b/src/features/task/delete/api/useDeleteTask.ts index 01196f727..51b0c3730 100644 --- a/src/features/task/delete/api/useDeleteTask.ts +++ b/src/features/task/delete/api/useDeleteTask.ts @@ -1,9 +1,9 @@ import { useMutation, useQueryClient } from '@tanstack/react-query' import { parse } from 'valibot' -import { BoardContracts, useGetParamBoardId } from '@/entities/board' +import { useGetParamBoardId } from '@/entities/board' -import { deleteTaskMutation, getBoardByIdQueryKey } from '@/shared/api' +import { deleteTaskMutation, getBoardByIdQueryKey, vBoard } from '@/shared/api' export const useDeleteTask = () => { const queryClient = useQueryClient() @@ -23,19 +23,16 @@ export const useDeleteTask = () => { const previousBoard = queryClient.getQueryData(boardQueryKey) - const parsedPreviousBoard = parse( - BoardContracts.BoardSchema, - previousBoard - ) + const parsedPreviousBoard = parse(vBoard, previousBoard) queryClient.setQueryData(boardQueryKey, oldBoard => { if (!oldBoard) return oldBoard - const parsedOldBoard = parse(BoardContracts.BoardSchema, oldBoard) + const parsedOldBoard = parse(vBoard, oldBoard) return { ...oldBoard, - columns: parsedOldBoard.columns.map(column => ({ + columns: parsedOldBoard.columns?.map(column => ({ ...column, tasks: column.tasks?.filter(task => task.id !== taskId) })) diff --git a/src/features/task/edit/model/contract.ts b/src/features/task/edit/model/contract.ts index e8b49fa2d..ed9f77e36 100644 --- a/src/features/task/edit/model/contract.ts +++ b/src/features/task/edit/model/contract.ts @@ -1,6 +1,6 @@ import * as v from 'valibot' -import { TASK_PRIORITIES } from '@/entities/task' +import { vTaskPriority } from '@/shared/api' export const EditTaskSchema = v.object({ title: v.optional( @@ -21,11 +21,12 @@ export const EditTaskSchema = v.object({ v.minLength(3, 'Please enter at least 3 characters.') ) ]), - priority: v.optional(v.picklist(TASK_PRIORITIES)), + priority: v.optional(vTaskPriority), labels: v.optional(v.array(v.string())), deadline: v.pipe( v.nullish(v.date()), - v.transform(v => v ?? null) + v.transform(v => v ?? null), + v.transform(v => v?.toISOString()) ) }) diff --git a/src/features/task/edit/model/types.ts b/src/features/task/edit/model/types.ts index b0bebce36..c460e9561 100644 --- a/src/features/task/edit/model/types.ts +++ b/src/features/task/edit/model/types.ts @@ -1,9 +1,6 @@ -import type { TaskTypes } from '@/entities/task' +import type { Task } from '@/shared/api' -export type EditTaskFormValues = Omit< - TaskTypes.TaskSchema, - 'id' | 'columnId' | 'order' -> +export type EditTaskFormValues = Omit export type EditTaskData = { taskId: string diff --git a/src/features/task/edit/ui/EditTaskForm.tsx b/src/features/task/edit/ui/EditTaskForm.tsx index 65e87012c..d98d3117b 100644 --- a/src/features/task/edit/ui/EditTaskForm.tsx +++ b/src/features/task/edit/ui/EditTaskForm.tsx @@ -34,7 +34,7 @@ export const EditTaskForm = ({ const form = useAppForm(EditTaskSchema, { defaultValues: { ...formValues, - deadline: formValues.deadline ?? undefined, + deadline: formValues.deadline ? new Date(formValues.deadline) : undefined, description: formValues.description ?? '', labels: formValues.labels?.map(l => l.id) ?? [] } diff --git a/src/features/task/filter/lib/useFilteredTasks.ts b/src/features/task/filter/lib/useFilteredTasks.ts index 8eb1a42f2..1d985d4c8 100644 --- a/src/features/task/filter/lib/useFilteredTasks.ts +++ b/src/features/task/filter/lib/useFilteredTasks.ts @@ -1,4 +1,4 @@ -import type { TaskTypes } from '@/entities/task' +import type { Task } from '@/shared/api' import type { IFuseOptions } from 'fuse.js' import { useMemo } from 'react' @@ -16,7 +16,7 @@ import { useTaskFilters } from './useTaskFilters' const today = startOfToday() const nextWeek = addDays(today, 7) -const fuseOptions: IFuseOptions = { +const fuseOptions: IFuseOptions = { keys: [ { name: 'title', weight: 0.7 }, { name: 'description', weight: 0.3 } @@ -26,7 +26,7 @@ const fuseOptions: IFuseOptions = { minMatchCharLength: 2 } -export const useFilteredTasks = (tasks: TaskTypes.TasksSchema) => { +export const useFilteredTasks = (tasks: Task[]) => { const { priority, deadline, search } = useTaskFilters() const fuse = useMemo(() => new Fuse(tasks, fuseOptions), [tasks]) diff --git a/src/features/task/filter/ui/PriorityFilter.tsx b/src/features/task/filter/ui/PriorityFilter.tsx index 4120faced..75aa6ed1f 100644 --- a/src/features/task/filter/ui/PriorityFilter.tsx +++ b/src/features/task/filter/ui/PriorityFilter.tsx @@ -1,5 +1,6 @@ -import { getTaskPriorityColor, TASK_PRIORITIES } from '@/entities/task' +import { getTaskPriorityColor } from '@/entities/task' +import { TaskPriority } from '@/shared/api' import { capitalize } from '@/shared/lib' import { RadioGroup, RadioGroupItem } from '@/shared/ui' @@ -13,7 +14,7 @@ export const PriorityFilter = () => { className='flex-col' value={priority ?? ''} onValueChange={v => handleParamsChange('priority', v)}> - {TASK_PRIORITIES.map(priority => ( + {Object.values(TaskPriority).map(priority => (
diff --git a/src/widgets/kanban-board/ui/task-list/MemoizedTask.tsx b/src/widgets/kanban-board/ui/task-list/MemoizedTask.tsx index 9b10ced56..6d409ae5a 100644 --- a/src/widgets/kanban-board/ui/task-list/MemoizedTask.tsx +++ b/src/widgets/kanban-board/ui/task-list/MemoizedTask.tsx @@ -29,7 +29,6 @@ export const MemoizedTask = memo(({ task }: MemoizedTaskProps) => {
completeTask({ taskId: task.id, completed: v }) } diff --git a/src/widgets/sidebar/ui/SidebarBoardListItem.tsx b/src/widgets/sidebar/ui/SidebarBoardListItem.tsx index 067a45336..cc6e6bbad 100644 --- a/src/widgets/sidebar/ui/SidebarBoardListItem.tsx +++ b/src/widgets/sidebar/ui/SidebarBoardListItem.tsx @@ -29,9 +29,9 @@ export const SidebarBoardListItem = ({ board }: SidebarBoardListItemProps) => { Date: Fri, 17 Jul 2026 18:33:21 +0100 Subject: [PATCH 06/39] feat(settings): add accent color customization --- src/app/index.css | 13 ++--- src/app/providers/RouteProvider.tsx | 2 +- src/entities/label/config/color-map.ts | 45 ++++++++++++---- src/entities/label/config/colors.ts | 0 src/entities/label/ui/FormLabelsCombobox.tsx | 5 +- src/entities/settings/index.ts | 3 +- src/entities/settings/lib/resetSettings.ts | 8 +++ .../model/{contracts.ts => contract.ts} | 0 src/entities/settings/model/types.ts | 2 +- src/entities/settings/ui/Settings.tsx | 2 +- src/entities/settings/ui/SettingsSync.tsx | 10 +++- src/entities/task/index.ts | 2 +- src/entities/task/lib/priority-colors.ts | 6 +-- .../task/model/{contracts.ts => contract.ts} | 0 src/entities/task/model/types.ts | 2 +- src/entities/task/ui/Task.tsx | 2 +- src/entities/user/index.ts | 2 +- .../user/model/{contracts.ts => contract.ts} | 0 src/entities/user/model/types.ts | 2 +- .../label/add/ui/LabelColorPicker.tsx | 2 +- .../settings/general/ui/GeneralSettings.tsx | 53 ++++++++++++++++++- .../user/need-help/ui/NeedHelpCard.tsx | 2 +- .../user/need-help/ui/NeedHelpDialog.tsx | 2 +- src/pages/settings/ui/SettingsPage.tsx | 2 +- src/pages/welcome/ui/SocialButton.tsx | 5 +- src/pages/welcome/ui/WelcomePage.tsx | 8 +-- src/shared/ui/AlertDialog.tsx | 2 +- src/shared/ui/Button.tsx | 2 +- src/shared/ui/Calendar.tsx | 6 +-- src/shared/ui/Checkbox.tsx | 2 +- src/shared/ui/Combobox.tsx | 6 +-- src/shared/ui/Dialog.tsx | 2 +- src/shared/ui/Input.tsx | 2 +- src/shared/ui/Select.tsx | 10 ++-- src/shared/ui/Switch.tsx | 2 +- src/shared/ui/TextArea.tsx | 2 +- src/widgets/kanban-board/ui/EmptyBoard.tsx | 4 +- .../kanban-board/ui/filters/Filters.tsx | 4 +- src/widgets/sidebar/api/useLogoutUser.ts | 2 + .../ui/SidebarBoardCreationSection.tsx | 2 +- .../sidebar/ui/SidebarBoardListItem.tsx | 2 +- src/widgets/sidebar/ui/SidebarLogoutBtn.tsx | 2 +- 42 files changed, 167 insertions(+), 65 deletions(-) delete mode 100644 src/entities/label/config/colors.ts create mode 100644 src/entities/settings/lib/resetSettings.ts rename src/entities/settings/model/{contracts.ts => contract.ts} (100%) rename src/entities/task/model/{contracts.ts => contract.ts} (100%) rename src/entities/user/model/{contracts.ts => contract.ts} (100%) diff --git a/src/app/index.css b/src/app/index.css index c668cce79..268576150 100644 --- a/src/app/index.css +++ b/src/app/index.css @@ -45,11 +45,12 @@ --color-white: #ffffff; --color-white-soft: #fcfcfc; --color-white-muted: #f6f6f7; - --color-brand: #bedbb0; - --color-brand-light: #9dc888; - --color-blue: #8fa1d0; + --color-accent: var(--accent-color); + --color-priority-low: #9bb7d4; + --color-priority-medium: #e5a8b7; --color-red: #dc2626; - --color-pink: #e09cb5; + --color-green: #bedbb0; + --color-green-light: #9dc888; --text-*: initial; --text-xs: 8px; @@ -210,11 +211,11 @@ } @utility styled-outline { - @apply outline-brand outline-2; + @apply outline-accent outline-2; } @utility bg-soft-green { - @apply to-brand bg-linear-180 from-white; + @apply to-green bg-linear-180 from-white; } @utility disable-text-selection { diff --git a/src/app/providers/RouteProvider.tsx b/src/app/providers/RouteProvider.tsx index 58a614c59..8869139db 100644 --- a/src/app/providers/RouteProvider.tsx +++ b/src/app/providers/RouteProvider.tsx @@ -9,7 +9,7 @@ export const RouterProvider = () => ( defaultPendingComponent={() => (
- +
)} diff --git a/src/entities/label/config/color-map.ts b/src/entities/label/config/color-map.ts index ef7934410..8a364b5a1 100644 --- a/src/entities/label/config/color-map.ts +++ b/src/entities/label/config/color-map.ts @@ -15,13 +15,40 @@ export const LABEL_COLOR_MAP: Record = { gray: 'bg-gray-100 text-gray-700 dark:bg-gray-500/15 dark:text-gray-300' } as const -export const COLOR_MAP: Record = { - blue: 'bg-blue-500', - purple: 'bg-violet-500', - green: 'bg-emerald-500', - red: 'bg-rose-500', - yellow: 'bg-amber-500', - cyan: 'bg-cyan-500', - indigo: 'bg-indigo-500', - gray: 'bg-gray-500' +export const COLOR_MAP: Record< + LabelColor, + { className: string; value: string } +> = { + blue: { + className: 'bg-blue-500', + value: '#8FB8D8' + }, + purple: { + className: 'bg-violet-500', + value: '#C3A6D8' + }, + green: { + className: 'bg-emerald-500', + value: '#BEDBB0' + }, + red: { + className: 'bg-rose-500', + value: '#E7A6A6' + }, + yellow: { + className: 'bg-amber-500', + value: '#F2D28B' + }, + cyan: { + className: 'bg-cyan-500', + value: '#91D4D8' + }, + indigo: { + className: 'bg-indigo-500', + value: '#A7B1E8' + }, + gray: { + className: 'bg-gray-500', + value: '#B8B8B8' + } } as const diff --git a/src/entities/label/config/colors.ts b/src/entities/label/config/colors.ts deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/entities/label/ui/FormLabelsCombobox.tsx b/src/entities/label/ui/FormLabelsCombobox.tsx index 3f9598faa..b75c36dd1 100644 --- a/src/entities/label/ui/FormLabelsCombobox.tsx +++ b/src/entities/label/ui/FormLabelsCombobox.tsx @@ -93,7 +93,10 @@ export const FormLabelsCombobox = ({ key={item.id} value={item.id}> {item.name} diff --git a/src/entities/settings/index.ts b/src/entities/settings/index.ts index 0fde4c0f1..3480e1c40 100644 --- a/src/entities/settings/index.ts +++ b/src/entities/settings/index.ts @@ -1,5 +1,6 @@ export * as SettingsTypes from './model/types' -export * as SettingsContracts from './model/contracts' +export * as SettingsContracts from './model/contract' export { Settings } from './ui/Settings' +export { resetSettings } from './lib/resetSettings' export { SettingsSync } from './ui/SettingsSync' export { useSettings } from './model/useSettings' diff --git a/src/entities/settings/lib/resetSettings.ts b/src/entities/settings/lib/resetSettings.ts new file mode 100644 index 000000000..34be385be --- /dev/null +++ b/src/entities/settings/lib/resetSettings.ts @@ -0,0 +1,8 @@ +const DEFAULT_ACCENT_COLOR = '#bedbb0' + +export const resetSettings = () => { + const root = document.documentElement + + root.dataset.pointerCursors = 'true' + root.style.setProperty('--accent-color', DEFAULT_ACCENT_COLOR) +} diff --git a/src/entities/settings/model/contracts.ts b/src/entities/settings/model/contract.ts similarity index 100% rename from src/entities/settings/model/contracts.ts rename to src/entities/settings/model/contract.ts diff --git a/src/entities/settings/model/types.ts b/src/entities/settings/model/types.ts index 8d29be804..9a343a97c 100644 --- a/src/entities/settings/model/types.ts +++ b/src/entities/settings/model/types.ts @@ -1,4 +1,4 @@ import type { InferOutput } from 'valibot' -import type { SettingsSchema } from './contracts' +import type { SettingsSchema } from './contract' export type SettingsSchema = InferOutput diff --git a/src/entities/settings/ui/Settings.tsx b/src/entities/settings/ui/Settings.tsx index 5a54ec8f5..fd24897a5 100644 --- a/src/entities/settings/ui/Settings.tsx +++ b/src/entities/settings/ui/Settings.tsx @@ -18,7 +18,7 @@ type ChildrenProps = { const Section = ({ title, children, isLoading }: SectionProps) => (
{isLoading ? (
diff --git a/src/entities/settings/ui/SettingsSync.tsx b/src/entities/settings/ui/SettingsSync.tsx index 0da375847..4b35dad9b 100644 --- a/src/entities/settings/ui/SettingsSync.tsx +++ b/src/entities/settings/ui/SettingsSync.tsx @@ -1,7 +1,10 @@ import { useEffect } from 'react' +import { COLOR_MAP } from '@/entities/label' + import { DEFAULT_THEME, resolveTheme } from '@/shared/config' +import { resetSettings } from '../lib/resetSettings' import { useSettings } from '../model/useSettings' export const SettingsSync = () => { @@ -9,8 +12,11 @@ export const SettingsSync = () => { const cursor = settings?.general?.usePointerCursors const theme = settings?.general?.theme + const accentColor = COLOR_MAP[settings?.general?.accentColor ?? 'blue'].value useEffect(() => { + if (!settings) return resetSettings() + const root = document.documentElement root.dataset.pointerCursors = String(cursor ?? false) @@ -32,7 +38,9 @@ export const SettingsSync = () => { } root.dataset.theme = String(theme ?? 'light') - }, [cursor, settings, theme]) + + document.documentElement.style.setProperty('--accent-color', accentColor) + }, [accentColor, cursor, settings, theme]) return null } diff --git a/src/entities/task/index.ts b/src/entities/task/index.ts index 9b82b7ac8..63d71643b 100644 --- a/src/entities/task/index.ts +++ b/src/entities/task/index.ts @@ -1,5 +1,5 @@ export * as TaskTypes from './model/types' -export * as TaskContracts from './model/contracts' +export * as TaskContracts from './model/contract' export { TASK_DEADLINES } from './config/deadline' export { getTaskPriorityColor } from './lib/priority-colors' export { formatDeadlineDate } from './lib/format-deadline-date' diff --git a/src/entities/task/lib/priority-colors.ts b/src/entities/task/lib/priority-colors.ts index 2b1111a52..07c340888 100644 --- a/src/entities/task/lib/priority-colors.ts +++ b/src/entities/task/lib/priority-colors.ts @@ -1,9 +1,9 @@ import type { TaskPriority } from '@/shared/api' const taskPriorityColors: Record = { - low: 'bg-blue', - medium: 'bg-pink', - high: 'bg-brand', + low: 'bg-priority-low', + medium: 'bg-priority-medium', + high: 'bg-green', without: 'bg-black/30 dark:bg-white/30' } diff --git a/src/entities/task/model/contracts.ts b/src/entities/task/model/contract.ts similarity index 100% rename from src/entities/task/model/contracts.ts rename to src/entities/task/model/contract.ts diff --git a/src/entities/task/model/types.ts b/src/entities/task/model/types.ts index cf7bcbe1f..6bcf6a60c 100644 --- a/src/entities/task/model/types.ts +++ b/src/entities/task/model/types.ts @@ -1,4 +1,4 @@ import type { InferOutput } from 'valibot' -import type { TaskSearchSchema } from './contracts' +import type { TaskSearchSchema } from './contract' export type TaskSearchSchema = InferOutput diff --git a/src/entities/task/ui/Task.tsx b/src/entities/task/ui/Task.tsx index 1199df2d6..446822fd0 100644 --- a/src/entities/task/ui/Task.tsx +++ b/src/entities/task/ui/Task.tsx @@ -208,7 +208,7 @@ const TaskDeadlineTodayIndicator = ({ className }: { className?: string }) => { task.deadline && isToday(task.deadline) && ( ) ) diff --git a/src/entities/user/index.ts b/src/entities/user/index.ts index 81901aac9..675872be2 100644 --- a/src/entities/user/index.ts +++ b/src/entities/user/index.ts @@ -1,5 +1,5 @@ export * as UserTypes from './model/types' -export * as UserContracts from './model/contracts' +export * as UserContracts from './model/contract' export { defaultAvatarUrl } from './config/default-avatar-url' export { useMetaThemeColor } from './lib/useMetaThemeColor' export { sessionQueries } from './api/queries' diff --git a/src/entities/user/model/contracts.ts b/src/entities/user/model/contract.ts similarity index 100% rename from src/entities/user/model/contracts.ts rename to src/entities/user/model/contract.ts diff --git a/src/entities/user/model/types.ts b/src/entities/user/model/types.ts index 3321fddb1..5afd3c689 100644 --- a/src/entities/user/model/types.ts +++ b/src/entities/user/model/types.ts @@ -1,4 +1,4 @@ import type { InferOutput } from 'valibot' -import type { UserSchema } from './contracts' +import type { UserSchema } from './contract' export type UserSchema = InferOutput diff --git a/src/features/label/add/ui/LabelColorPicker.tsx b/src/features/label/add/ui/LabelColorPicker.tsx index 98701e2a6..99e6b4e17 100644 --- a/src/features/label/add/ui/LabelColorPicker.tsx +++ b/src/features/label/add/ui/LabelColorPicker.tsx @@ -24,7 +24,7 @@ export const LabelColorPicker = () => { className={cn( `focus-visible:styled-outline block size-6 rounded-xl transition-transform data-checked:scale-125`, - COLOR_MAP[color] + COLOR_MAP[color].className )} /> diff --git a/src/features/settings/general/ui/GeneralSettings.tsx b/src/features/settings/general/ui/GeneralSettings.tsx index 773701c83..e786baf3e 100644 --- a/src/features/settings/general/ui/GeneralSettings.tsx +++ b/src/features/settings/general/ui/GeneralSettings.tsx @@ -1,7 +1,9 @@ import { ChevronDownIcon } from 'lucide-react' +import { COLOR_MAP } from '@/entities/label' import { Settings } from '@/entities/settings' +import { LabelColor } from '@/shared/api' import { THEMES } from '@/shared/config' import { capitalize } from '@/shared/lib' import { @@ -37,9 +39,10 @@ export const GeneralSettings = () => { + + + Accent color + + Set the primary color used throughout the app. + + + + + + Pointer cursors diff --git a/src/features/user/need-help/ui/NeedHelpCard.tsx b/src/features/user/need-help/ui/NeedHelpCard.tsx index 8a81381f5..0d5579ab0 100644 --- a/src/features/user/need-help/ui/NeedHelpCard.tsx +++ b/src/features/user/need-help/ui/NeedHelpCard.tsx @@ -15,7 +15,7 @@ export const NeedHelpCard = ({ children }: NeedHelpCardProps) => ( />

If you need help with - TaskPro, check out our support + TaskPro, check out our support resources or reach out to our customer support team.

{children} diff --git a/src/features/user/need-help/ui/NeedHelpDialog.tsx b/src/features/user/need-help/ui/NeedHelpDialog.tsx index 948551749..5bb9d2307 100644 --- a/src/features/user/need-help/ui/NeedHelpDialog.tsx +++ b/src/features/user/need-help/ui/NeedHelpDialog.tsx @@ -22,7 +22,7 @@ export const NeedHelpDialog = () => { diff --git a/src/pages/settings/ui/SettingsPage.tsx b/src/pages/settings/ui/SettingsPage.tsx index 741fcf4bb..dcdc52c04 100644 --- a/src/pages/settings/ui/SettingsPage.tsx +++ b/src/pages/settings/ui/SettingsPage.tsx @@ -8,7 +8,7 @@ export const SettingsPage = () => { return (

Settings

    diff --git a/src/pages/welcome/ui/SocialButton.tsx b/src/pages/welcome/ui/SocialButton.tsx index 5318bab7e..a49c1b8b5 100644 --- a/src/pages/welcome/ui/SocialButton.tsx +++ b/src/pages/welcome/ui/SocialButton.tsx @@ -9,8 +9,9 @@ type SocialButtonProps = { export const SocialButton = ({ provider, onClick }: SocialButtonProps) => (