From 36e720920b773f2791870d4f51bc682f56f3440a Mon Sep 17 00:00:00 2001 From: Philip Okugbe Date: Sun, 13 Oct 2024 17:09:45 +0100 Subject: [PATCH] fix: bug fixes (#397) * Add more html page titles * Make tables responsive * fix react query keys * Add tooltip to sidebar toggle * fix: trim inputs * fix inputs --- .../src/components/common/recent-changes.tsx | 30 +-- .../components/layouts/global/app-header.tsx | 47 +++-- .../components/ui/sidebar-toggle-button.tsx | 42 ++-- .../auth/components/invite-sign-up-form.tsx | 2 +- .../auth/components/setup-workspace-form.tsx | 4 +- .../group/components/create-group-form.tsx | 2 +- .../features/group/components/group-list.tsx | 119 ++++++----- .../group/components/group-members.tsx | 136 ++++++------ .../src/features/group/queries/group-query.ts | 24 ++- .../space/components/create-space-form.tsx | 3 +- .../space/components/settings-modal.tsx | 80 +++---- .../features/space/components/space-list.tsx | 92 ++++---- .../space/components/space-members.tsx | 198 +++++++++--------- .../src/features/space/queries/space-query.ts | 6 +- .../components/workspace-invites-table.tsx | 88 ++++---- .../components/workspace-members-table.tsx | 100 ++++----- apps/client/src/lib/config.ts | 4 + .../client/src/pages/auth/forgot-password.tsx | 3 +- apps/client/src/pages/auth/invite-signup.tsx | 7 +- apps/client/src/pages/auth/login.tsx | 3 +- apps/client/src/pages/auth/password-reset.tsx | 5 +- .../client/src/pages/auth/setup-workspace.tsx | 3 +- apps/client/src/pages/dashboard/home.tsx | 23 +- .../settings/account/account-preferences.tsx | 23 +- .../settings/account/account-settings.tsx | 5 + .../src/pages/settings/group/group-info.tsx | 19 +- .../src/pages/settings/group/groups.tsx | 5 + .../src/pages/settings/space/spaces.tsx | 27 ++- .../settings/workspace/workspace-members.tsx | 97 +++++---- .../settings/workspace/workspace-settings.tsx | 17 +- apps/client/src/pages/space/space-home.tsx | 27 ++- apps/client/src/pages/welcome.tsx | 23 -- .../core/auth/dto/create-admin-user.dto.ts | 11 +- .../src/core/auth/dto/create-user.dto.ts | 6 +- .../core/auth/dto/verify-user-token.dto.ts | 2 +- .../src/core/group/dto/create-group.dto.ts | 2 + .../src/core/space/dto/create-space.dto.ts | 2 + .../workspace/dto/create-workspace.dto.ts | 6 +- 38 files changed, 681 insertions(+), 612 deletions(-) delete mode 100644 apps/client/src/pages/welcome.tsx diff --git a/apps/client/src/components/common/recent-changes.tsx b/apps/client/src/components/common/recent-changes.tsx index de7f023..0f89c81 100644 --- a/apps/client/src/components/common/recent-changes.tsx +++ b/apps/client/src/components/common/recent-changes.tsx @@ -4,25 +4,25 @@ import { UnstyledButton, Badge, Table, - ScrollArea, ActionIcon, } from '@mantine/core'; -import { Link } from 'react-router-dom'; +import {Link} from 'react-router-dom'; import PageListSkeleton from '@/components/ui/page-list-skeleton.tsx'; -import { buildPageUrl } from '@/features/page/page.utils.ts'; -import { formattedDate } from '@/lib/time.ts'; -import { useRecentChangesQuery } from '@/features/page/queries/page-query.ts'; -import { IconFileDescription } from '@tabler/icons-react'; -import { getSpaceUrl } from '@/lib/config.ts'; +import {buildPageUrl} from '@/features/page/page.utils.ts'; +import {formattedDate} from '@/lib/time.ts'; +import {useRecentChangesQuery} from '@/features/page/queries/page-query.ts'; +import {IconFileDescription} from '@tabler/icons-react'; +import {getSpaceUrl} from '@/lib/config.ts'; interface Props { spaceId?: string; } -export default function RecentChanges({ spaceId }: Props) { - const { data: pages, isLoading, isError } = useRecentChangesQuery(spaceId); + +export default function RecentChanges({spaceId}: Props) { + const {data: pages, isLoading, isError} = useRecentChangesQuery(spaceId); if (isLoading) { - return ; + return ; } if (isError) { @@ -30,7 +30,7 @@ export default function RecentChanges({ spaceId }: Props) { } return pages && pages.items.length > 0 ? ( - + {pages.items.map((page) => ( @@ -43,7 +43,7 @@ export default function RecentChanges({ spaceId }: Props) { {page.icon || ( - + )} @@ -60,14 +60,14 @@ export default function RecentChanges({ spaceId }: Props) { variant="light" component={Link} to={getSpaceUrl(page?.space.slug)} - style={{ cursor: 'pointer' }} + style={{cursor: 'pointer'}} > {page?.space.name} )} - + {formattedDate(page.updatedAt)} @@ -75,7 +75,7 @@ export default function RecentChanges({ spaceId }: Props) { ))}
-
+ ) : ( No pages yet diff --git a/apps/client/src/components/layouts/global/app-header.tsx b/apps/client/src/components/layouts/global/app-header.tsx index c83bf4f..1eeb735 100644 --- a/apps/client/src/components/layouts/global/app-header.tsx +++ b/apps/client/src/components/layouts/global/app-header.tsx @@ -1,18 +1,18 @@ -import { Group, Text } from "@mantine/core"; +import {Group, Text, Tooltip} from "@mantine/core"; import classes from "./app-header.module.css"; import React from "react"; import TopMenu from "@/components/layouts/global/top-menu.tsx"; -import { Link } from "react-router-dom"; +import {Link} from "react-router-dom"; import APP_ROUTE from "@/lib/app-route.ts"; -import { useAtom } from "jotai/index"; +import {useAtom} from "jotai/index"; import { desktopSidebarAtom, mobileSidebarAtom, } from "@/components/layouts/global/hooks/atoms/sidebar-atom.ts"; -import { useToggleSidebar } from "@/components/layouts/global/hooks/hooks/use-toggle-sidebar.ts"; +import {useToggleSidebar} from "@/components/layouts/global/hooks/hooks/use-toggle-sidebar.ts"; import SidebarToggle from "@/components/ui/sidebar-toggle-button.tsx"; -const links = [{ link: APP_ROUTE.HOME, label: "Home" }]; +const links = [{link: APP_ROUTE.HOME, label: "Home"}]; export function AppHeader() { const [mobileOpened] = useAtom(mobileSidebarAtom); @@ -35,28 +35,33 @@ export function AppHeader() { {!isHomeRoute && ( <> - + - + + + + + + )} @@ -69,7 +74,7 @@ export function AppHeader() { - + diff --git a/apps/client/src/components/ui/sidebar-toggle-button.tsx b/apps/client/src/components/ui/sidebar-toggle-button.tsx index 0c17459..be89484 100644 --- a/apps/client/src/components/ui/sidebar-toggle-button.tsx +++ b/apps/client/src/components/ui/sidebar-toggle-button.tsx @@ -1,15 +1,9 @@ +import React from "react"; import { IconLayoutSidebarRightCollapse, - IconLayoutSidebarRightExpand, + IconLayoutSidebarRightExpand } from "@tabler/icons-react"; -import { - ActionIcon, - BoxProps, - ElementProps, - MantineColor, - MantineSize, -} from "@mantine/core"; -import React from "react"; +import { ActionIcon, BoxProps, ElementProps, MantineColor, MantineSize } from "@mantine/core"; export interface SidebarToggleProps extends BoxProps, ElementProps<"button"> { size?: MantineSize | `compact-${MantineSize}` | (string & {}); @@ -17,18 +11,18 @@ export interface SidebarToggleProps extends BoxProps, ElementProps<"button"> { opened?: boolean; } -export default function SidebarToggle({ - opened, - size = "sm", - ...others -}: SidebarToggleProps) { - return ( - - {opened ? ( - - ) : ( - - )} - - ); -} +const SidebarToggle = React.forwardRef( + ({ opened, size = "sm", ...others }, ref) => { + return ( + + {opened ? ( + + ) : ( + + )} + + ); + } +); + +export default SidebarToggle; diff --git a/apps/client/src/features/auth/components/invite-sign-up-form.tsx b/apps/client/src/features/auth/components/invite-sign-up-form.tsx index 81752f5..2690d00 100644 --- a/apps/client/src/features/auth/components/invite-sign-up-form.tsx +++ b/apps/client/src/features/auth/components/invite-sign-up-form.tsx @@ -19,7 +19,7 @@ import { useGetInvitationQuery } from "@/features/workspace/queries/workspace-qu import { useRedirectIfAuthenticated } from "@/features/auth/hooks/use-redirect-if-authenticated.ts"; const formSchema = z.object({ - name: z.string().min(2), + name: z.string().trim().min(1), password: z.string().min(8), }); diff --git a/apps/client/src/features/auth/components/setup-workspace-form.tsx b/apps/client/src/features/auth/components/setup-workspace-form.tsx index bb62918..6be94f5 100644 --- a/apps/client/src/features/auth/components/setup-workspace-form.tsx +++ b/apps/client/src/features/auth/components/setup-workspace-form.tsx @@ -15,8 +15,8 @@ import useAuth from "@/features/auth/hooks/use-auth"; import classes from "@/features/auth/components/auth.module.css"; const formSchema = z.object({ - workspaceName: z.string().min(2).max(60), - name: z.string().min(2).max(60), + workspaceName: z.string().trim().min(3).max(50), + name: z.string().min(1).max(50), email: z .string() .min(1, { message: "email is required" }) diff --git a/apps/client/src/features/group/components/create-group-form.tsx b/apps/client/src/features/group/components/create-group-form.tsx index 2773064..0c6df49 100644 --- a/apps/client/src/features/group/components/create-group-form.tsx +++ b/apps/client/src/features/group/components/create-group-form.tsx @@ -7,7 +7,7 @@ import { useNavigate } from "react-router-dom"; import { MultiUserSelect } from "@/features/group/components/multi-user-select.tsx"; const formSchema = z.object({ - name: z.string().min(2).max(50), + name: z.string().trim().min(2).max(50), description: z.string().max(500), }); diff --git a/apps/client/src/features/group/components/group-list.tsx b/apps/client/src/features/group/components/group-list.tsx index ae5d647..0da1d02 100644 --- a/apps/client/src/features/group/components/group-list.tsx +++ b/apps/client/src/features/group/components/group-list.tsx @@ -1,69 +1,72 @@ -import { Table, Group, Text, Anchor } from "@mantine/core"; -import { useGetGroupsQuery } from "@/features/group/queries/group-query"; +import {Table, Group, Text, Anchor} from "@mantine/core"; +import {useGetGroupsQuery} from "@/features/group/queries/group-query"; import React from "react"; -import { Link } from "react-router-dom"; -import { IconGroupCircle } from "@/components/icons/icon-people-circle.tsx"; +import {Link} from "react-router-dom"; +import {IconGroupCircle} from "@/components/icons/icon-people-circle.tsx"; export default function GroupList() { - const { data, isLoading } = useGetGroupsQuery(); + const {data, isLoading} = useGetGroupsQuery(); return ( <> {data && ( - - - - Group - Members - - - - - {data?.items.map((group, index) => ( - - - - - -
- - {group.name} - - - {group.description} - -
-
-
-
- - - - {group.memberCount} members - - + +
+ + + Group + Members - ))} - -
+ + + + {data?.items.map((group, index) => ( + + + + + +
+ + {group.name} + + + {group.description} + +
+
+
+
+ + + + {group.memberCount} members + + +
+ ))} +
+ + )} ); diff --git a/apps/client/src/features/group/components/group-members.tsx b/apps/client/src/features/group/components/group-members.tsx index 157f4d7..62b5714 100644 --- a/apps/client/src/features/group/components/group-members.tsx +++ b/apps/client/src/features/group/components/group-members.tsx @@ -1,20 +1,20 @@ -import { Group, Table, Text, Badge, Menu, ActionIcon } from "@mantine/core"; +import {Group, Table, Text, Badge, Menu, ActionIcon} from "@mantine/core"; import { useGroupMembersQuery, useRemoveGroupMemberMutation, } from "@/features/group/queries/group-query"; -import { useParams } from "react-router-dom"; +import {useParams} from "react-router-dom"; import React from "react"; -import { IconDots } from "@tabler/icons-react"; -import { modals } from "@mantine/modals"; -import { CustomAvatar } from "@/components/ui/custom-avatar.tsx"; +import {IconDots} from "@tabler/icons-react"; +import {modals} from "@mantine/modals"; +import {CustomAvatar} from "@/components/ui/custom-avatar.tsx"; import useUserRole from "@/hooks/use-user-role.tsx"; export default function GroupMembersList() { - const { groupId } = useParams(); - const { data, isLoading } = useGroupMembersQuery(groupId); + const {groupId} = useParams(); + const {data, isLoading} = useGroupMembersQuery(groupId); const removeGroupMember = useRemoveGroupMemberMutation(); - const { isAdmin } = useUserRole(); + const {isAdmin} = useUserRole(); const onRemove = async (userId: string) => { const memberToRemove = { @@ -34,72 +34,74 @@ export default function GroupMembersList() {
), centered: true, - labels: { confirm: "Delete", cancel: "Cancel" }, - confirmProps: { color: "red" }, + labels: {confirm: "Delete", cancel: "Cancel"}, + confirmProps: {color: "red"}, onConfirm: () => onRemove(userId), }); return ( <> {data && ( - - - - User - Status - - - - - - {data?.items.map((user, index) => ( - - - - -
- - {user.name} - - - {user.email} - -
-
-
- - - Active - - - - {isAdmin && ( - - - - - - - - - openRemoveModal(user.id)}> - Remove group member - - - - )} - + +
+ + + User + Status + - ))} - -
+ + + + {data?.items.map((user, index) => ( + + + + +
+ + {user.name} + + + {user.email} + +
+
+
+ + + Active + + + + {isAdmin && ( + + + + + + + + + openRemoveModal(user.id)}> + Remove group member + + + + )} + +
+ ))} +
+ + )} ); diff --git a/apps/client/src/features/group/queries/group-query.ts b/apps/client/src/features/group/queries/group-query.ts index b61314a..ac113f8 100644 --- a/apps/client/src/features/group/queries/group-query.ts +++ b/apps/client/src/features/group/queries/group-query.ts @@ -29,24 +29,22 @@ export function useGetGroupsQuery( export function useGroupQuery(groupId: string): UseQueryResult { return useQuery({ - queryKey: ['groups', groupId], + queryKey: ['group', groupId], queryFn: () => getGroupById(groupId), enabled: !!groupId, }); } -export function useGroupMembersQuery(groupId: string) { - return useQuery({ - queryKey: ['groupMembers', groupId], - queryFn: () => getGroupMembers(groupId), - enabled: !!groupId, - }); -} - export function useCreateGroupMutation() { + const queryClient = useQueryClient(); + return useMutation>({ mutationFn: (data) => createGroup(data), onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: ['groups'], + }); + notifications.show({ message: 'Group created successfully' }); }, onError: () => { @@ -96,6 +94,14 @@ export function useDeleteGroupMutation() { }); } +export function useGroupMembersQuery(groupId: string) { + return useQuery({ + queryKey: ['groupMembers', groupId], + queryFn: () => getGroupMembers(groupId), + enabled: !!groupId, + }); +} + export function useAddGroupMemberMutation() { const queryClient = useQueryClient(); diff --git a/apps/client/src/features/space/components/create-space-form.tsx b/apps/client/src/features/space/components/create-space-form.tsx index 29fe46f..15049c1 100644 --- a/apps/client/src/features/space/components/create-space-form.tsx +++ b/apps/client/src/features/space/components/create-space-form.tsx @@ -8,9 +8,10 @@ import { computeSpaceSlug } from "@/lib"; import { getSpaceUrl } from "@/lib/config.ts"; const formSchema = z.object({ - name: z.string().min(2).max(50), + name: z.string().trim().min(2).max(50), slug: z .string() + .trim() .min(2) .max(50) .regex( diff --git a/apps/client/src/features/space/components/settings-modal.tsx b/apps/client/src/features/space/components/settings-modal.tsx index e2f4ed7..cac9cda 100644 --- a/apps/client/src/features/space/components/settings-modal.tsx +++ b/apps/client/src/features/space/components/settings-modal.tsx @@ -1,10 +1,10 @@ -import { Modal, Tabs, rem, Group, ScrollArea } from "@mantine/core"; +import {Modal, Tabs, rem, Group, ScrollArea, Text} from "@mantine/core"; import SpaceMembersList from "@/features/space/components/space-members.tsx"; import AddSpaceMembersModal from "@/features/space/components/add-space-members-modal.tsx"; -import React, { useMemo } from "react"; +import React, {useMemo} from "react"; import SpaceDetails from "@/features/space/components/space-details.tsx"; -import { useSpaceQuery } from "@/features/space/queries/space-query.ts"; -import { useSpaceAbility } from "@/features/space/permissions/use-space-ability.ts"; +import {useSpaceQuery} from "@/features/space/queries/space-query.ts"; +import {useSpaceAbility} from "@/features/space/permissions/use-space-ability.ts"; import { SpaceCaslAction, SpaceCaslSubject, @@ -17,11 +17,11 @@ interface SpaceSettingsModalProps { } export default function SpaceSettingsModal({ - spaceId, - opened, - onClose, -}: SpaceSettingsModalProps) { - const { data: space, isLoading } = useSpaceQuery(spaceId); + spaceId, + opened, + onClose, + }: SpaceSettingsModalProps) { + const {data: space, isLoading} = useSpaceQuery(spaceId); const spaceRules = space?.membership?.permissions; const spaceAbility = useMemo(() => useSpaceAbility(spaceRules), [spaceRules]); @@ -37,14 +37,16 @@ export default function SpaceSettingsModal({ xOffset={0} mah={400} > - - + + - {space?.name} - + + {space?.name} + + -
+
@@ -55,34 +57,32 @@ export default function SpaceSettingsModal({ - - - - + + + - - - {spaceAbility.can( - SpaceCaslAction.Manage, - SpaceCaslSubject.Member, - ) && } - + + + {spaceAbility.can( + SpaceCaslAction.Manage, + SpaceCaslSubject.Member, + ) && } + - - - + +
diff --git a/apps/client/src/features/space/components/space-list.tsx b/apps/client/src/features/space/components/space-list.tsx index 48253fd..cb271b6 100644 --- a/apps/client/src/features/space/components/space-list.tsx +++ b/apps/client/src/features/space/components/space-list.tsx @@ -1,13 +1,13 @@ -import { Table, Group, Text, Avatar } from "@mantine/core"; -import React, { useState } from "react"; -import { useGetSpacesQuery } from "@/features/space/queries/space-query.ts"; +import {Table, Group, Text, Avatar} from "@mantine/core"; +import React, {useState} from "react"; +import {useGetSpacesQuery} from "@/features/space/queries/space-query.ts"; import SpaceSettingsModal from "@/features/space/components/settings-modal.tsx"; -import { useDisclosure } from "@mantine/hooks"; -import { formatMemberCount } from "@/lib"; +import {useDisclosure} from "@mantine/hooks"; +import {formatMemberCount} from "@/lib"; export default function SpaceList() { - const { data, isLoading } = useGetSpacesQuery(); - const [opened, { open, close }] = useDisclosure(false); + const {data, isLoading} = useGetSpacesQuery(); + const [opened, {open, close}] = useDisclosure(false); const [selectedSpaceId, setSelectedSpaceId] = useState(null); const handleClick = (spaceId: string) => { @@ -18,44 +18,48 @@ export default function SpaceList() { return ( <> {data && ( - - - - Space - Members - - - - - {data?.items.map((space, index) => ( - handleClick(space.id)} - > - - - -
- - {space.name} - - - {space.description} - -
-
-
- - {formatMemberCount(space.memberCount)} + +
+ + + Space + Members - ))} - -
+ + + + {data?.items.map((space, index) => ( + handleClick(space.id)} + > + + + +
+ + {space.name} + + + {space.description} + +
+
+
+ + + {formatMemberCount(space.memberCount)} + +
+ ))} +
+ + )} {selectedSpaceId && ( diff --git a/apps/client/src/features/space/components/space-members.tsx b/apps/client/src/features/space/components/space-members.tsx index 763cedc..d6861dd 100644 --- a/apps/client/src/features/space/components/space-members.tsx +++ b/apps/client/src/features/space/components/space-members.tsx @@ -1,32 +1,34 @@ -import { Group, Table, Text, Menu, ActionIcon } from "@mantine/core"; +import {Group, Table, Text, Menu, ActionIcon} from "@mantine/core"; import React from "react"; -import { IconDots } from "@tabler/icons-react"; -import { modals } from "@mantine/modals"; -import { CustomAvatar } from "@/components/ui/custom-avatar.tsx"; +import {IconDots} from "@tabler/icons-react"; +import {modals} from "@mantine/modals"; +import {CustomAvatar} from "@/components/ui/custom-avatar.tsx"; import { useChangeSpaceMemberRoleMutation, useRemoveSpaceMemberMutation, useSpaceMembersQuery, } from "@/features/space/queries/space-query.ts"; -import { IconGroupCircle } from "@/components/icons/icon-people-circle.tsx"; -import { IRemoveSpaceMember } from "@/features/space/types/space.types.ts"; +import {IconGroupCircle} from "@/components/icons/icon-people-circle.tsx"; +import {IRemoveSpaceMember} from "@/features/space/types/space.types.ts"; import RoleSelectMenu from "@/components/ui/role-select-menu.tsx"; import { getSpaceRoleLabel, spaceRoleData, } from "@/features/space/types/space-role-data.ts"; -import { formatMemberCount } from "@/lib"; +import {formatMemberCount} from "@/lib"; type MemberType = "user" | "group"; + interface SpaceMembersProps { spaceId: string; readOnly?: boolean; } + export default function SpaceMembersList({ - spaceId, - readOnly, -}: SpaceMembersProps) { - const { data, isLoading } = useSpaceMembersQuery(spaceId); + spaceId, + readOnly, + }: SpaceMembersProps) { + const {data, isLoading} = useSpaceMembersQuery(spaceId); const removeSpaceMember = useRemoveSpaceMemberMutation(); const changeSpaceMemberRoleMutation = useChangeSpaceMemberRoleMutation(); @@ -85,99 +87,101 @@ export default function SpaceMembersList({ ), centered: true, - labels: { confirm: "Remove", cancel: "Cancel" }, - confirmProps: { color: "red" }, + labels: {confirm: "Remove", cancel: "Cancel"}, + confirmProps: {color: "red"}, onConfirm: () => onRemove(memberId, type), }); return ( <> {data && ( - - - - Member - Role - - - - - - {data?.items.map((member, index) => ( - - - - {member.type === "user" && ( - - )} - - {member.type === "group" && } - -
- - {member?.name} - - - {member.type == "user" && member?.email} - - {member.type == "group" && - `Group - ${formatMemberCount(member?.memberCount)}`} - -
-
-
- - - - handleRoleChange( - member.id, - member.type, - newRole, - member.role, - ) - } - disabled={readOnly} - /> - - - - {!readOnly && ( - - - - - - - - - - openRemoveModal(member.id, member.type) - } - > - Remove space member - - - - )} - + +
+ + + Member + Role + - ))} - -
+ + + + {data?.items.map((member, index) => ( + + + + {member.type === "user" && ( + + )} + + {member.type === "group" && } + +
+ + {member?.name} + + + {member.type == "user" && member?.email} + + {member.type == "group" && + `Group - ${formatMemberCount(member?.memberCount)}`} + +
+
+
+ + + + handleRoleChange( + member.id, + member.type, + newRole, + member.role, + ) + } + disabled={readOnly} + /> + + + + {!readOnly && ( + + + + + + + + + + openRemoveModal(member.id, member.type) + } + > + Remove space member + + + + )} + +
+ ))} +
+ + )} ); diff --git a/apps/client/src/features/space/queries/space-query.ts b/apps/client/src/features/space/queries/space-query.ts index b7c6945..e1fa7a1 100644 --- a/apps/client/src/features/space/queries/space-query.ts +++ b/apps/client/src/features/space/queries/space-query.ts @@ -36,7 +36,7 @@ export function useGetSpacesQuery( export function useSpaceQuery(spaceId: string): UseQueryResult { return useQuery({ - queryKey: ['spaces', spaceId], + queryKey: ['space', spaceId], queryFn: () => getSpaceById(spaceId), enabled: !!spaceId, staleTime: 5 * 60 * 1000, @@ -65,7 +65,7 @@ export function useGetSpaceBySlugQuery( spaceId: string ): UseQueryResult { return useQuery({ - queryKey: ['spaces', spaceId], + queryKey: ['space', spaceId], queryFn: () => getSpaceById(spaceId), enabled: !!spaceId, staleTime: 5 * 60 * 1000, @@ -111,7 +111,7 @@ export function useDeleteSpaceMutation() { if (variables.slug) { queryClient.removeQueries({ - queryKey: ['spaces', variables.slug], + queryKey: ['space', variables.slug], exact: true, }); } diff --git a/apps/client/src/features/workspace/components/members/components/workspace-invites-table.tsx b/apps/client/src/features/workspace/components/members/components/workspace-invites-table.tsx index a7fbb9c..6517cd5 100644 --- a/apps/client/src/features/workspace/components/members/components/workspace-invites-table.tsx +++ b/apps/client/src/features/workspace/components/members/components/workspace-invites-table.tsx @@ -1,62 +1,64 @@ -import { Group, Table, Avatar, Text, Alert } from "@mantine/core"; -import { useWorkspaceInvitationsQuery } from "@/features/workspace/queries/workspace-query.ts"; +import {Group, Table, Avatar, Text, Alert} from "@mantine/core"; +import {useWorkspaceInvitationsQuery} from "@/features/workspace/queries/workspace-query.ts"; import React from "react"; -import { getUserRoleLabel } from "@/features/workspace/types/user-role-data.ts"; +import {getUserRoleLabel} from "@/features/workspace/types/user-role-data.ts"; import InviteActionMenu from "@/features/workspace/components/members/components/invite-action-menu.tsx"; -import { IconInfoCircle } from "@tabler/icons-react"; -import { formattedDate } from "@/lib/time.ts"; +import {IconInfoCircle} from "@tabler/icons-react"; +import {formattedDate, timeAgo} from "@/lib/time.ts"; import useUserRole from "@/hooks/use-user-role.tsx"; export default function WorkspaceInvitesTable() { - const { data, isLoading } = useWorkspaceInvitationsQuery({ + const {data, isLoading} = useWorkspaceInvitationsQuery({ limit: 100, }); - const { isAdmin } = useUserRole(); + const {isAdmin} = useUserRole(); return ( <> - }> + }> Invited members who are yet to accept their invitation will appear here. {data && ( <> - - - - Email - Role - Date - - - - - {data?.items.map((invitation, index) => ( - - - - -
- - {invitation.email} - -
-
-
- - {getUserRoleLabel(invitation.role)} - - {formattedDate(invitation.createdAt)} - - - {isAdmin && ( - - )} - + +
+ + + Email + Role + Date - ))} - -
+ + + + {data?.items.map((invitation, index) => ( + + + + +
+ + {invitation.email} + +
+
+
+ + {getUserRoleLabel(invitation.role)} + + {timeAgo(invitation.createdAt)} + + + {isAdmin && ( + + )} + +
+ ))} +
+ + )} diff --git a/apps/client/src/features/workspace/components/members/components/workspace-members-table.tsx b/apps/client/src/features/workspace/components/members/components/workspace-members-table.tsx index c99ce6b..58f2ce8 100644 --- a/apps/client/src/features/workspace/components/members/components/workspace-members-table.tsx +++ b/apps/client/src/features/workspace/components/members/components/workspace-members-table.tsx @@ -1,9 +1,9 @@ -import { Group, Table, Text, Badge } from "@mantine/core"; +import {Group, Table, Text, Badge} from "@mantine/core"; import { useChangeMemberRoleMutation, useWorkspaceMembersQuery, } from "@/features/workspace/queries/workspace-query.ts"; -import { CustomAvatar } from "@/components/ui/custom-avatar.tsx"; +import {CustomAvatar} from "@/components/ui/custom-avatar.tsx"; import React from "react"; import RoleSelectMenu from "@/components/ui/role-select-menu.tsx"; import { @@ -11,14 +11,14 @@ import { userRoleData, } from "@/features/workspace/types/user-role-data.ts"; import useUserRole from "@/hooks/use-user-role.tsx"; -import { UserRole } from "@/lib/types.ts"; +import {UserRole} from "@/lib/types.ts"; export default function WorkspaceMembersTable() { - const { data, isLoading } = useWorkspaceMembersQuery({ limit: 100 }); + const {data, isLoading} = useWorkspaceMembersQuery({limit: 100}); const changeMemberRoleMutation = useChangeMemberRoleMutation(); - const { isAdmin, isOwner } = useUserRole(); + const {isAdmin, isOwner} = useUserRole(); - const assignableUserRoles = isOwner ? userRoleData : userRoleData.filter((role) => role.value !== UserRole.OWNER); + const assignableUserRoles = isOwner ? userRoleData : userRoleData.filter((role) => role.value !== UserRole.OWNER); const handleRoleChange = async ( userId: string, @@ -40,50 +40,52 @@ export default function WorkspaceMembersTable() { return ( <> {data && ( - - - - User - Status - Role - - - - - {data?.items.map((user, index) => ( - - - - -
- - {user.name} - - - {user.email} - -
-
-
- - - Active - - - - - handleRoleChange(user.id, user.role, newRole) - } - disabled={!isAdmin} - /> - + +
+ + + User + Status + Role - ))} - -
+ + + + {data?.items.map((user, index) => ( + + + + +
+ + {user.name} + + + {user.email} + +
+
+
+ + + Active + + + + + handleRoleChange(user.id, user.role, newRole) + } + disabled={!isAdmin} + /> + +
+ ))} +
+ + )} ); diff --git a/apps/client/src/lib/config.ts b/apps/client/src/lib/config.ts index 64c7c4e..f6ff3fb 100644 --- a/apps/client/src/lib/config.ts +++ b/apps/client/src/lib/config.ts @@ -6,6 +6,10 @@ declare global { } } +export function getAppName(): string{ + return 'Docmost'; +} + export function getAppUrl(): string { //let appUrl = window.CONFIG?.APP_URL || process.env.APP_URL; diff --git a/apps/client/src/pages/auth/forgot-password.tsx b/apps/client/src/pages/auth/forgot-password.tsx index 0872fe3..94826c1 100644 --- a/apps/client/src/pages/auth/forgot-password.tsx +++ b/apps/client/src/pages/auth/forgot-password.tsx @@ -1,11 +1,12 @@ import { ForgotPasswordForm } from "@/features/auth/components/forgot-password-form"; +import { getAppName } from "@/lib/config"; import { Helmet } from "react-helmet-async"; export default function ForgotPassword() { return ( <> - Forgot Password - Docmost + Forgot Password - {getAppName()} diff --git a/apps/client/src/pages/auth/invite-signup.tsx b/apps/client/src/pages/auth/invite-signup.tsx index c113bb9..cb0057c 100644 --- a/apps/client/src/pages/auth/invite-signup.tsx +++ b/apps/client/src/pages/auth/invite-signup.tsx @@ -1,12 +1,13 @@ import { Helmet } from "react-helmet-async"; import { InviteSignUpForm } from "@/features/auth/components/invite-sign-up-form.tsx"; +import {getAppName} from "@/lib/config.ts"; export default function InviteSignup() { return ( <> - - Invitation Signup - Docmost - + + Invitation Signuo - {getAppName()} + ); diff --git a/apps/client/src/pages/auth/login.tsx b/apps/client/src/pages/auth/login.tsx index 68ba2c7..c3f8cc9 100644 --- a/apps/client/src/pages/auth/login.tsx +++ b/apps/client/src/pages/auth/login.tsx @@ -1,11 +1,12 @@ import { LoginForm } from "@/features/auth/components/login-form"; import { Helmet } from "react-helmet-async"; +import {getAppName} from "@/lib/config.ts"; export default function LoginPage() { return ( <> - Login - Docmost + Login - {getAppName()} diff --git a/apps/client/src/pages/auth/password-reset.tsx b/apps/client/src/pages/auth/password-reset.tsx index 0f37086..a01c681 100644 --- a/apps/client/src/pages/auth/password-reset.tsx +++ b/apps/client/src/pages/auth/password-reset.tsx @@ -4,6 +4,7 @@ import { Link, useSearchParams } from "react-router-dom"; import { useVerifyUserTokenQuery } from "@/features/auth/queries/auth-query"; import { Button, Container, Group, Text } from "@mantine/core"; import APP_ROUTE from "@/lib/app-route"; +import {getAppName} from "@/lib/config.ts"; export default function PasswordReset() { const [searchParams] = useSearchParams(); @@ -21,7 +22,7 @@ export default function PasswordReset() { return ( <> - Password Reset - Docmost + Password Reset - {getAppName()} @@ -45,7 +46,7 @@ export default function PasswordReset() { return ( <> - Password Reset - Docmost + Password Reset - {getAppName()} diff --git a/apps/client/src/pages/auth/setup-workspace.tsx b/apps/client/src/pages/auth/setup-workspace.tsx index 8f1ba9b..009ae10 100644 --- a/apps/client/src/pages/auth/setup-workspace.tsx +++ b/apps/client/src/pages/auth/setup-workspace.tsx @@ -3,6 +3,7 @@ import { SetupWorkspaceForm } from "@/features/auth/components/setup-workspace-f import { Helmet } from "react-helmet-async"; import React, { useEffect } from "react"; import { useNavigate } from "react-router-dom"; +import {getAppName} from "@/lib/config.ts"; export default function SetupWorkspace() { const { @@ -32,7 +33,7 @@ export default function SetupWorkspace() { return ( <> - Setup Workspace - Docmost + Setup Workspace - {getAppName()} diff --git a/apps/client/src/pages/dashboard/home.tsx b/apps/client/src/pages/dashboard/home.tsx index fc99cb1..0a201ef 100644 --- a/apps/client/src/pages/dashboard/home.tsx +++ b/apps/client/src/pages/dashboard/home.tsx @@ -1,15 +1,22 @@ -import { Container, Space } from "@mantine/core"; +import {Container, Space} from "@mantine/core"; import HomeTabs from "@/features/home/components/home-tabs"; import SpaceGrid from "@/features/space/components/space-grid.tsx"; +import {getAppName} from "@/lib/config.ts"; +import {Helmet} from "react-helmet-async"; export default function Home() { - return ( - - + return ( + <> + + Home - {getAppName()} + + + - + - - - ); + + + + ); } diff --git a/apps/client/src/pages/settings/account/account-preferences.tsx b/apps/client/src/pages/settings/account/account-preferences.tsx index d312e18..91bad82 100644 --- a/apps/client/src/pages/settings/account/account-preferences.tsx +++ b/apps/client/src/pages/settings/account/account-preferences.tsx @@ -1,15 +1,20 @@ 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"; +import {Divider} from "@mantine/core"; +import {getAppName} from "@/lib/config.ts"; +import {Helmet} from "react-helmet-async"; export default function AccountPreferences() { - return ( - <> - - - - - - ); + return ( + <> + + Preferences - {getAppName()} + + + + + + + ); } diff --git a/apps/client/src/pages/settings/account/account-settings.tsx b/apps/client/src/pages/settings/account/account-settings.tsx index c115d25..044adc0 100644 --- a/apps/client/src/pages/settings/account/account-settings.tsx +++ b/apps/client/src/pages/settings/account/account-settings.tsx @@ -4,10 +4,15 @@ import ChangePassword from "@/features/user/components/change-password"; import { Divider } from "@mantine/core"; import AccountAvatar from "@/features/user/components/account-avatar"; import SettingsTitle from "@/components/settings/settings-title.tsx"; +import {getAppName} from "@/lib/config.ts"; +import {Helmet} from "react-helmet-async"; export default function AccountSettings() { return ( <> + + My Profile - {getAppName()} + diff --git a/apps/client/src/pages/settings/group/group-info.tsx b/apps/client/src/pages/settings/group/group-info.tsx index 7a22e7d..e5a079a 100644 --- a/apps/client/src/pages/settings/group/group-info.tsx +++ b/apps/client/src/pages/settings/group/group-info.tsx @@ -1,13 +1,18 @@ import SettingsTitle from "@/components/settings/settings-title.tsx"; import GroupMembersList from "@/features/group/components/group-members"; import GroupDetails from "@/features/group/components/group-details"; +import {getAppName} from "@/lib/config.ts"; +import {Helmet} from "react-helmet-async"; export default function GroupInfo() { - return ( - <> - - - - - ); + return ( + <> + + Manage Group - {getAppName()} + + + + + + ); } diff --git a/apps/client/src/pages/settings/group/groups.tsx b/apps/client/src/pages/settings/group/groups.tsx index 719233f..c3fb201 100644 --- a/apps/client/src/pages/settings/group/groups.tsx +++ b/apps/client/src/pages/settings/group/groups.tsx @@ -3,12 +3,17 @@ import SettingsTitle from "@/components/settings/settings-title.tsx"; import { Group } from "@mantine/core"; import CreateGroupModal from "@/features/group/components/create-group-modal"; import useUserRole from "@/hooks/use-user-role.tsx"; +import {getAppName} from "@/lib/config.ts"; +import {Helmet} from "react-helmet-async"; export default function Groups() { const { isAdmin } = useUserRole(); return ( <> + + Groups - {getAppName()} + diff --git a/apps/client/src/pages/settings/space/spaces.tsx b/apps/client/src/pages/settings/space/spaces.tsx index 3cfc4e7..6eb2cc7 100644 --- a/apps/client/src/pages/settings/space/spaces.tsx +++ b/apps/client/src/pages/settings/space/spaces.tsx @@ -1,21 +1,26 @@ import SettingsTitle from "@/components/settings/settings-title.tsx"; import SpaceList from "@/features/space/components/space-list.tsx"; import useUserRole from "@/hooks/use-user-role.tsx"; -import { Group } from "@mantine/core"; +import {Group} from "@mantine/core"; import CreateSpaceModal from "@/features/space/components/create-space-modal.tsx"; +import {getAppName} from "@/lib/config.ts"; +import {Helmet} from "react-helmet-async"; export default function Spaces() { - const { isAdmin } = useUserRole(); + const {isAdmin} = useUserRole(); - return ( - <> - + return ( + <> + + Spaces - {getAppName()} + + - - {isAdmin && } - + + {isAdmin && } + - - - ); + + + ); } diff --git a/apps/client/src/pages/settings/workspace/workspace-members.tsx b/apps/client/src/pages/settings/workspace/workspace-members.tsx index 6ae01aa..5cdd861 100644 --- a/apps/client/src/pages/settings/workspace/workspace-members.tsx +++ b/apps/client/src/pages/settings/workspace/workspace-members.tsx @@ -1,62 +1,67 @@ import WorkspaceInviteModal from "@/features/workspace/components/members/components/workspace-invite-modal"; -import { Group, SegmentedControl, Space, Text } from "@mantine/core"; +import {Group, SegmentedControl, Space, Text} from "@mantine/core"; import WorkspaceMembersTable from "@/features/workspace/components/members/components/workspace-members-table"; import SettingsTitle from "@/components/settings/settings-title.tsx"; -import { useEffect, useState } from "react"; -import { useNavigate, useSearchParams } from "react-router-dom"; +import {useEffect, useState} from "react"; +import {useNavigate, useSearchParams} from "react-router-dom"; import WorkspaceInvitesTable from "@/features/workspace/components/members/components/workspace-invites-table.tsx"; import useUserRole from "@/hooks/use-user-role.tsx"; +import {getAppName} from "@/lib/config.ts"; +import {Helmet} from "react-helmet-async"; export default function WorkspaceMembers() { - const [segmentValue, setSegmentValue] = useState("members"); - const [searchParams] = useSearchParams(); - const { isAdmin } = useUserRole(); - const navigate = useNavigate(); + const [segmentValue, setSegmentValue] = useState("members"); + const [searchParams] = useSearchParams(); + const {isAdmin} = useUserRole(); + const navigate = useNavigate(); - useEffect(() => { - const currentTab = searchParams.get("tab"); - if (currentTab === "invites") { - setSegmentValue(currentTab); - } - }, [searchParams.get("tab")]); + useEffect(() => { + const currentTab = searchParams.get("tab"); + if (currentTab === "invites") { + setSegmentValue(currentTab); + } + }, [searchParams.get("tab")]); - const handleSegmentChange = (value: string) => { - setSegmentValue(value); - if (value === "invites") { - navigate(`?tab=${value}`); - } else { - navigate(""); - } - }; + const handleSegmentChange = (value: string) => { + setSegmentValue(value); + if (value === "invites") { + navigate(`?tab=${value}`); + } else { + navigate(""); + } + }; - return ( - <> - + return ( + <> + + Members - {getAppName()} + + - {/* */} - {/* */} + {/* */} + {/* */} - - + + - {isAdmin && } - + {isAdmin && } + - + - {segmentValue === "invites" ? ( - - ) : ( - - )} - - ); + {segmentValue === "invites" ? ( + + ) : ( + + )} + + ); } diff --git a/apps/client/src/pages/settings/workspace/workspace-settings.tsx b/apps/client/src/pages/settings/workspace/workspace-settings.tsx index 66e93eb..cdc361c 100644 --- a/apps/client/src/pages/settings/workspace/workspace-settings.tsx +++ b/apps/client/src/pages/settings/workspace/workspace-settings.tsx @@ -1,11 +1,16 @@ import SettingsTitle from "@/components/settings/settings-title.tsx"; import WorkspaceNameForm from "@/features/workspace/components/settings/components/workspace-name-form"; +import {getAppName} from "@/lib/config.ts"; +import {Helmet} from "react-helmet-async"; export default function WorkspaceSettings() { - return ( - <> - - - - ); + return ( + <> + + Workspace Settings - {getAppName()} + + + + + ); } diff --git a/apps/client/src/pages/space/space-home.tsx b/apps/client/src/pages/space/space-home.tsx index 86ffe5f..0b5622f 100644 --- a/apps/client/src/pages/space/space-home.tsx +++ b/apps/client/src/pages/space/space-home.tsx @@ -1,15 +1,22 @@ -import { Container } from "@mantine/core"; +import {Container} from "@mantine/core"; import SpaceHomeTabs from "@/features/space/components/space-home-tabs.tsx"; -import { useParams } from "react-router-dom"; -import { useGetSpaceBySlugQuery } from "@/features/space/queries/space-query.ts"; +import {useParams} from "react-router-dom"; +import {useGetSpaceBySlugQuery} from "@/features/space/queries/space-query.ts"; +import {getAppName} from "@/lib/config.ts"; +import {Helmet} from "react-helmet-async"; export default function SpaceHome() { - const { spaceSlug } = useParams(); - const { data: space } = useGetSpaceBySlugQuery(spaceSlug); + const {spaceSlug} = useParams(); + const {data: space} = useGetSpaceBySlugQuery(spaceSlug); - return ( - - {space && } - - ); + return ( + <> + + {space?.name || 'Overview'} - {getAppName()} + + + {space && } + + + ); } diff --git a/apps/client/src/pages/welcome.tsx b/apps/client/src/pages/welcome.tsx deleted file mode 100644 index 6c0d0e5..0000000 --- a/apps/client/src/pages/welcome.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { Title, Text, Stack } from '@mantine/core'; -import { ThemeToggle } from '@/components/theme-toggle'; - -export function Welcome() { - return ( - - - <Text - inherit - variant="gradient" - component="span" - gradient={{ from: 'pink', to: 'yellow' }} - > - Welcome - </Text> - - - Welcome to something new and interesting. - - - - ); -} diff --git a/apps/server/src/core/auth/dto/create-admin-user.dto.ts b/apps/server/src/core/auth/dto/create-admin-user.dto.ts index c85cd3c..fb6b4cb 100644 --- a/apps/server/src/core/auth/dto/create-admin-user.dto.ts +++ b/apps/server/src/core/auth/dto/create-admin-user.dto.ts @@ -1,15 +1,18 @@ import { IsNotEmpty, IsString, MaxLength, MinLength } from 'class-validator'; import { CreateUserDto } from './create-user.dto'; +import {Transform, TransformFnParams} from "class-transformer"; export class CreateAdminUserDto extends CreateUserDto { @IsNotEmpty() - @MinLength(3) - @MaxLength(35) + @MinLength(1) + @MaxLength(50) + @Transform(({ value }: TransformFnParams) => value?.trim()) name: string; @IsNotEmpty() - @MinLength(4) - @MaxLength(35) + @MinLength(3) + @MaxLength(50) @IsString() + @Transform(({ value }: TransformFnParams) => value?.trim()) workspaceName: string; } diff --git a/apps/server/src/core/auth/dto/create-user.dto.ts b/apps/server/src/core/auth/dto/create-user.dto.ts index 5a224c0..359fb1c 100644 --- a/apps/server/src/core/auth/dto/create-user.dto.ts +++ b/apps/server/src/core/auth/dto/create-user.dto.ts @@ -6,12 +6,14 @@ import { MaxLength, MinLength, } from 'class-validator'; +import {Transform, TransformFnParams} from "class-transformer"; export class CreateUserDto { @IsOptional() - @MinLength(2) - @MaxLength(60) + @MinLength(1) + @MaxLength(50) @IsString() + @Transform(({ value }: TransformFnParams) => value?.trim()) name: string; @IsNotEmpty() diff --git a/apps/server/src/core/auth/dto/verify-user-token.dto.ts b/apps/server/src/core/auth/dto/verify-user-token.dto.ts index f789a3d..59600c5 100644 --- a/apps/server/src/core/auth/dto/verify-user-token.dto.ts +++ b/apps/server/src/core/auth/dto/verify-user-token.dto.ts @@ -1,4 +1,4 @@ -import { IsString, MinLength } from 'class-validator'; +import { IsString } from 'class-validator'; export class VerifyUserTokenDto { @IsString() diff --git a/apps/server/src/core/group/dto/create-group.dto.ts b/apps/server/src/core/group/dto/create-group.dto.ts index c49a270..2efdad3 100644 --- a/apps/server/src/core/group/dto/create-group.dto.ts +++ b/apps/server/src/core/group/dto/create-group.dto.ts @@ -7,11 +7,13 @@ import { MaxLength, MinLength, } from 'class-validator'; +import {Transform, TransformFnParams} from "class-transformer"; export class CreateGroupDto { @MinLength(2) @MaxLength(50) @IsString() + @Transform(({ value }: TransformFnParams) => value?.trim()) name: string; @IsOptional() diff --git a/apps/server/src/core/space/dto/create-space.dto.ts b/apps/server/src/core/space/dto/create-space.dto.ts index 0977323..bd7e668 100644 --- a/apps/server/src/core/space/dto/create-space.dto.ts +++ b/apps/server/src/core/space/dto/create-space.dto.ts @@ -5,11 +5,13 @@ import { MaxLength, MinLength, } from 'class-validator'; +import {Transform, TransformFnParams} from "class-transformer"; export class CreateSpaceDto { @MinLength(2) @MaxLength(50) @IsString() + @Transform(({ value }: TransformFnParams) => value?.trim()) name: string; @IsOptional() diff --git a/apps/server/src/core/workspace/dto/create-workspace.dto.ts b/apps/server/src/core/workspace/dto/create-workspace.dto.ts index c8ba193..7ad04e3 100644 --- a/apps/server/src/core/workspace/dto/create-workspace.dto.ts +++ b/apps/server/src/core/workspace/dto/create-workspace.dto.ts @@ -1,15 +1,17 @@ -import { IsOptional, IsString, MaxLength, MinLength } from 'class-validator'; +import {IsAlphanumeric, IsOptional, IsString, MaxLength, MinLength} from 'class-validator'; +import {Transform, TransformFnParams} from "class-transformer"; export class CreateWorkspaceDto { @MinLength(4) @MaxLength(64) @IsString() + @Transform(({ value }: TransformFnParams) => value?.trim()) name: string; @IsOptional() @MinLength(4) @MaxLength(30) - @IsString() + @IsAlphanumeric() hostname?: string; @IsOptional()