From 8f056d107110b7f62f1304de0fc59ace69c9a115 Mon Sep 17 00:00:00 2001 From: Philipinho <16838612+Philipinho@users.noreply.github.com> Date: Wed, 3 Jul 2024 11:00:42 +0100 Subject: [PATCH] add full page width preference --- .../components/layouts/global/top-menu.tsx | 47 ++++++------------- .../src/features/editor/full-editor.tsx | 15 +++++- .../features/editor/styles/editor.module.css | 1 - .../features/user/atoms/current-user-atom.ts | 13 ++++- .../user/components/page-width-pref.tsx | 42 +++++++++++++++++ .../src/features/user/types/user.types.ts | 9 +++- .../settings/account/account-preferences.tsx | 4 ++ .../src/core/user/dto/update-user.dto.ts | 6 ++- apps/server/src/core/user/user.service.ts | 17 +++++++ .../src/database/repos/user/user.repo.ts | 19 ++++++++ 10 files changed, 135 insertions(+), 38 deletions(-) create mode 100644 apps/client/src/features/user/components/page-width-pref.tsx diff --git a/apps/client/src/components/layouts/global/top-menu.tsx b/apps/client/src/components/layouts/global/top-menu.tsx index 8977580..cd7527c 100644 --- a/apps/client/src/components/layouts/global/top-menu.tsx +++ b/apps/client/src/components/layouts/global/top-menu.tsx @@ -1,5 +1,6 @@ -import { Avatar, Group, Menu, rem, UnstyledButton, Text } from "@mantine/core"; +import { Group, Menu, UnstyledButton, Text } from "@mantine/core"; import { + IconBrush, IconChevronDown, IconLogout, IconSettings, @@ -38,10 +39,7 @@ export default function TopMenu() { {workspace.name} - + @@ -51,12 +49,7 @@ export default function TopMenu() { - } + leftSection={} > Workspace settings @@ -64,12 +57,7 @@ export default function TopMenu() { - } + leftSection={} > Manage members @@ -98,27 +86,22 @@ export default function TopMenu() { - } + leftSection={} > My profile + } + > + My preferences + + - - } - > + }> Logout diff --git a/apps/client/src/features/editor/full-editor.tsx b/apps/client/src/features/editor/full-editor.tsx index d2d54eb..2368425 100644 --- a/apps/client/src/features/editor/full-editor.tsx +++ b/apps/client/src/features/editor/full-editor.tsx @@ -2,6 +2,9 @@ import classes from "@/features/editor/styles/editor.module.css"; import React from "react"; import { TitleEditor } from "@/features/editor/title-editor"; import PageEditor from "@/features/editor/page-editor"; +import { Container } from "@mantine/core"; +import { useAtom } from "jotai/index"; +import { userAtom } from "@/features/user/atoms/current-user-atom.ts"; const MemoizedTitleEditor = React.memo(TitleEditor); const MemoizedPageEditor = React.memo(PageEditor); @@ -21,8 +24,16 @@ export function FullEditor({ spaceSlug, editable, }: FullEditorProps) { + const [user] = useAtom(userAtom); + const fullPageWidth = user.settings?.preferences?.fullPageWidth; + return ( -
+ -
+ ); } diff --git a/apps/client/src/features/editor/styles/editor.module.css b/apps/client/src/features/editor/styles/editor.module.css index acc381a..c281ba3 100644 --- a/apps/client/src/features/editor/styles/editor.module.css +++ b/apps/client/src/features/editor/styles/editor.module.css @@ -1,5 +1,4 @@ .editor { - max-width: 800px; height: 100%; padding: 8px 20px; margin: 64px auto; diff --git a/apps/client/src/features/user/atoms/current-user-atom.ts b/apps/client/src/features/user/atoms/current-user-atom.ts index aa7ffff..49eab9d 100644 --- a/apps/client/src/features/user/atoms/current-user-atom.ts +++ b/apps/client/src/features/user/atoms/current-user-atom.ts @@ -1,5 +1,16 @@ import { atomWithStorage } from "jotai/utils"; import { ICurrentUser } from "@/features/user/types/user.types"; +import { focusAtom } from "jotai-optics"; -export const currentUserAtom = atomWithStorage("currentUser", null); +export const currentUserAtom = atomWithStorage( + "currentUser", + null, +); + +export const userAtom = focusAtom(currentUserAtom, (optic) => + optic.prop("user"), +); +export const workspaceAtom = focusAtom(currentUserAtom, (optic) => + optic.prop("workspace"), +); diff --git a/apps/client/src/features/user/components/page-width-pref.tsx b/apps/client/src/features/user/components/page-width-pref.tsx new file mode 100644 index 0000000..fecd231 --- /dev/null +++ b/apps/client/src/features/user/components/page-width-pref.tsx @@ -0,0 +1,42 @@ +import { Group, Text, Switch } from "@mantine/core"; +import { useAtom } from "jotai/index"; +import { userAtom } from "@/features/user/atoms/current-user-atom.ts"; +import { updateUser } from "@/features/user/services/user-service.ts"; +import React, { useState } from "react"; + +export default function PageWidthPref() { + return ( + +
+ Full page width + + Choose your preferred page width. + +
+ + +
+ ); +} + +function WidthToggle() { + const [user, setUser] = useAtom(userAtom); + const [checked, setChecked] = useState( + user.settings?.preferences?.fullPageWidth, + ); + + const handleChange = async (event: React.ChangeEvent) => { + const value = event.currentTarget.checked; + const updatedUser = await updateUser({ fullPageWidth: value }); + setChecked(value); + setUser(updatedUser); + }; + + return ( + + ); +} diff --git a/apps/client/src/features/user/types/user.types.ts b/apps/client/src/features/user/types/user.types.ts index 0a92c47..a729073 100644 --- a/apps/client/src/features/user/types/user.types.ts +++ b/apps/client/src/features/user/types/user.types.ts @@ -7,7 +7,7 @@ export interface IUser { emailVerifiedAt: Date; avatarUrl: string; timezone: string; - settings: any; + settings: IUserSettings; invitedById: string; lastLoginAt: string; lastActiveAt: Date; @@ -17,9 +17,16 @@ export interface IUser { workspaceId: string; deactivatedAt: Date; deletedAt: Date; + fullPageWidth: boolean; // used for update } export interface ICurrentUser { user: IUser; workspace: IWorkspace; } + +export interface IUserSettings { + preferences: { + fullPageWidth: boolean; + }; +} diff --git a/apps/client/src/pages/settings/account/account-preferences.tsx b/apps/client/src/pages/settings/account/account-preferences.tsx index 7e4df93..d312e18 100644 --- a/apps/client/src/pages/settings/account/account-preferences.tsx +++ b/apps/client/src/pages/settings/account/account-preferences.tsx @@ -1,11 +1,15 @@ import SettingsTitle from "@/components/settings/settings-title.tsx"; import AccountTheme from "@/features/user/components/account-theme.tsx"; +import PageWidthPref from "@/features/user/components/page-width-pref.tsx"; +import { Divider } from "@mantine/core"; export default function AccountPreferences() { return ( <> + + ); } diff --git a/apps/server/src/core/user/dto/update-user.dto.ts b/apps/server/src/core/user/dto/update-user.dto.ts index d83b4f2..c8ded9f 100644 --- a/apps/server/src/core/user/dto/update-user.dto.ts +++ b/apps/server/src/core/user/dto/update-user.dto.ts @@ -1,6 +1,6 @@ import { OmitType, PartialType } from '@nestjs/mapped-types'; import { CreateUserDto } from '../../auth/dto/create-user.dto'; -import { IsOptional, IsString } from 'class-validator'; +import { IsBoolean, IsOptional, IsString } from 'class-validator'; export class UpdateUserDto extends PartialType( OmitType(CreateUserDto, ['password'] as const), @@ -8,4 +8,8 @@ export class UpdateUserDto extends PartialType( @IsOptional() @IsString() avatarUrl: string; + + @IsOptional() + @IsBoolean() + fullPageWidth: boolean; } diff --git a/apps/server/src/core/user/user.service.ts b/apps/server/src/core/user/user.service.ts index 662fff7..9b6527b 100644 --- a/apps/server/src/core/user/user.service.ts +++ b/apps/server/src/core/user/user.service.ts @@ -20,10 +20,19 @@ export class UserService { workspaceId: string, ) { const user = await this.userRepo.findById(userId, workspaceId); + if (!user) { throw new NotFoundException('User not found'); } + // preference update + if (typeof updateUserDto.fullPageWidth !== 'undefined') { + return this.updateUserPageWidthPreference( + userId, + updateUserDto.fullPageWidth, + ); + } + if (updateUserDto.name) { user.name = updateUserDto.name; } @@ -42,4 +51,12 @@ export class UserService { await this.userRepo.updateUser(updateUserDto, userId, workspaceId); return user; } + + async updateUserPageWidthPreference(userId: string, fullPageWidth: boolean) { + return this.userRepo.updatePreference( + userId, + 'fullPageWidth', + fullPageWidth, + ); + } } diff --git a/apps/server/src/database/repos/user/user.repo.ts b/apps/server/src/database/repos/user/user.repo.ts index 8ec07a9..a521d67 100644 --- a/apps/server/src/database/repos/user/user.repo.ts +++ b/apps/server/src/database/repos/user/user.repo.ts @@ -15,6 +15,7 @@ import { executeWithPagination, PaginationResult, } from '@docmost/db/pagination/pagination'; +import { sql } from 'kysely'; @Injectable() export class UserRepo { @@ -157,6 +158,24 @@ export class UserRepo { return result; } + async updatePreference( + userId: string, + prefKey: string, + prefValue: string | boolean, + ) { + return await this.db + .updateTable('users') + .set({ + settings: sql`COALESCE(settings, '{}'::jsonb) + || jsonb_build_object('preferences', COALESCE(settings->'preferences', '{}'::jsonb) + || jsonb_build_object('${sql.raw(prefKey)}', ${sql.lit(prefValue)}))`, + updatedAt: new Date(), + }) + .where('id', '=', userId) + .returning(this.baseFields) + .executeTakeFirst(); + } + /* async getSpaceIds( workspaceId: string,