diff --git a/apps/client/src/components/common/recent-changes.tsx b/apps/client/src/components/common/recent-changes.tsx
index de7f023c..0f89c81e 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 c83bf4f8..1eeb735d 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 0c174592..be894840 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 81752f57..2690d00a 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 bb62918c..6be94f53 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 27730642..0c6df49b 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 ae5d6471..0da1d02f 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 157f4d7f..62b57149 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 && (
-
- )}
-
+
+
+
+
+ User
+ Status
+
- ))}
-
-
+
+
+
+ {data?.items.map((user, index) => (
+
+
+
+
+
+
+ {user.name}
+
+
+ {user.email}
+
+
+
+
+
+
+ Active
+
+
+
+ {isAdmin && (
+
+ )}
+
+
+ ))}
+
+
+
)}
>
);
diff --git a/apps/client/src/features/group/queries/group-query.ts b/apps/client/src/features/group/queries/group-query.ts
index b61314a3..ac113f87 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 29fe46f2..15049c1b 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 e2f4ed73..cac9cdaa 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 48253fdc..cb271b6e 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 763cedcc..d6861ddc 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 && (
-
- )}
-
+
+
+
+
+ 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 && (
+
+ )}
+
+
+ ))}
+
+
+
)}
>
);
diff --git a/apps/client/src/features/space/queries/space-query.ts b/apps/client/src/features/space/queries/space-query.ts
index b7c69454..e1fa7a12 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 a7fbb9c2..6517cd5b 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 c99ce6b2..58f2ce8f 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 64c7c4e4..f6ff3fb9 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 0872fe36..94826c1c 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 c113bb9c..cb0057c0 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 68ba2c7d..c3f8cc90 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 0f370862..a01c681c 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 8f1ba9bf..009ae10f 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 fc99cb10..0a201efa 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 d312e185..91bad824 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 c115d255..044adc00 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 7a22e7d8..e5a079a2 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 719233f6..c3fb201e 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 3cfc4e75..6eb2cc71 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 6ae01aa8..5cdd861b 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 66e93ebc..cdc361c8 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 86ffe5f3..0b5622fe 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 6c0d0e5e..00000000
--- 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 (
-
-
-
- Welcome
-
-
-
- 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 c85cd3ce..fb6b4cb9 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 5a224c02..359fb1c0 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 f789a3d4..59600c5a 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 c49a2709..2efdad35 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 09773235..bd7e6689 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 c8ba193a..7ad04e36 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()