mirror of
https://github.com/Shadowfita/docmost.git
synced 2025-11-13 08:12:32 +10:00
Update custom avatar
This commit is contained in:
@ -11,7 +11,7 @@ import { currentUserAtom } from "@/features/user/atoms/current-user-atom.ts";
|
|||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import APP_ROUTE from "@/lib/app-route.ts";
|
import APP_ROUTE from "@/lib/app-route.ts";
|
||||||
import useAuth from "@/features/auth/hooks/use-auth.ts";
|
import useAuth from "@/features/auth/hooks/use-auth.ts";
|
||||||
import { UserAvatar } from "@/components/ui/user-avatar.tsx";
|
import { CustomAvatar } from "@/components/ui/custom-avatar.tsx";
|
||||||
|
|
||||||
export default function TopMenu() {
|
export default function TopMenu() {
|
||||||
const [currentUser] = useAtom(currentUserAtom);
|
const [currentUser] = useAtom(currentUserAtom);
|
||||||
@ -29,12 +29,11 @@ export default function TopMenu() {
|
|||||||
<Menu.Target>
|
<Menu.Target>
|
||||||
<UnstyledButton>
|
<UnstyledButton>
|
||||||
<Group gap={7} wrap={"nowrap"}>
|
<Group gap={7} wrap={"nowrap"}>
|
||||||
<UserAvatar
|
<CustomAvatar
|
||||||
avatarUrl={workspace.logo}
|
avatarUrl={workspace.logo}
|
||||||
name={workspace.name}
|
name={workspace.name}
|
||||||
radius="xl"
|
variant="filled"
|
||||||
color="blue"
|
size="sm"
|
||||||
size={20}
|
|
||||||
/>
|
/>
|
||||||
<Text fw={500} size="sm" lh={1} mr={3}>
|
<Text fw={500} size="sm" lh={1} mr={3}>
|
||||||
{workspace.name}
|
{workspace.name}
|
||||||
@ -80,8 +79,7 @@ export default function TopMenu() {
|
|||||||
<Menu.Label>Account</Menu.Label>
|
<Menu.Label>Account</Menu.Label>
|
||||||
<Menu.Item component={Link} to={APP_ROUTE.SETTINGS.ACCOUNT.PROFILE}>
|
<Menu.Item component={Link} to={APP_ROUTE.SETTINGS.ACCOUNT.PROFILE}>
|
||||||
<Group wrap={"nowrap"}>
|
<Group wrap={"nowrap"}>
|
||||||
<UserAvatar
|
<CustomAvatar
|
||||||
radius="xl"
|
|
||||||
size={"sm"}
|
size={"sm"}
|
||||||
avatarUrl={user.avatarUrl}
|
avatarUrl={user.avatarUrl}
|
||||||
name={user.name}
|
name={user.name}
|
||||||
|
|||||||
32
apps/client/src/components/ui/custom-avatar.tsx
Normal file
32
apps/client/src/components/ui/custom-avatar.tsx
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Avatar } from "@mantine/core";
|
||||||
|
import { getAvatarUrl } from "@/lib/config.ts";
|
||||||
|
|
||||||
|
interface CustomAvatarProps {
|
||||||
|
avatarUrl: string;
|
||||||
|
name: string;
|
||||||
|
color?: string;
|
||||||
|
size?: string | number;
|
||||||
|
radius?: string | number;
|
||||||
|
variant?: string;
|
||||||
|
style?: any;
|
||||||
|
component?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CustomAvatar = React.forwardRef<
|
||||||
|
HTMLInputElement,
|
||||||
|
CustomAvatarProps
|
||||||
|
>(({ avatarUrl, name, ...props }: CustomAvatarProps, ref) => {
|
||||||
|
const avatarLink = getAvatarUrl(avatarUrl);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Avatar
|
||||||
|
ref={ref}
|
||||||
|
src={avatarLink}
|
||||||
|
name={name}
|
||||||
|
alt={name}
|
||||||
|
color="initials"
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
@ -1,35 +0,0 @@
|
|||||||
import React, { useRef } from "react";
|
|
||||||
import { Avatar } from "@mantine/core";
|
|
||||||
import { getAvatarUrl } from "@/lib/config.ts";
|
|
||||||
|
|
||||||
interface UserAvatarProps {
|
|
||||||
avatarUrl: string;
|
|
||||||
name: string;
|
|
||||||
color?: string;
|
|
||||||
size?: string | number;
|
|
||||||
radius?: string | number;
|
|
||||||
style?: any;
|
|
||||||
component?: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const UserAvatar = React.forwardRef<HTMLInputElement, UserAvatarProps>(
|
|
||||||
({ avatarUrl, name, ...props }: UserAvatarProps, ref) => {
|
|
||||||
const avatar = getAvatarUrl(avatarUrl);
|
|
||||||
|
|
||||||
const getInitials = (name: string) => {
|
|
||||||
const names = name?.split(" ");
|
|
||||||
return names
|
|
||||||
?.slice(0, 2)
|
|
||||||
.map((n) => n[0])
|
|
||||||
.join("");
|
|
||||||
};
|
|
||||||
|
|
||||||
return avatar ? (
|
|
||||||
<Avatar ref={ref} src={avatar} alt={name} radius="xl" {...props} />
|
|
||||||
) : (
|
|
||||||
<Avatar ref={ref} {...props}>
|
|
||||||
{getInitials(name)}
|
|
||||||
</Avatar>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
@ -13,7 +13,7 @@ import { currentUserAtom } from "@/features/user/atoms/current-user-atom";
|
|||||||
import { useCreateCommentMutation } from "@/features/comment/queries/comment-query";
|
import { useCreateCommentMutation } from "@/features/comment/queries/comment-query";
|
||||||
import { asideStateAtom } from "@/components/layouts/global/hooks/atoms/sidebar-atom";
|
import { asideStateAtom } from "@/components/layouts/global/hooks/atoms/sidebar-atom";
|
||||||
import { useEditor } from "@tiptap/react";
|
import { useEditor } from "@tiptap/react";
|
||||||
import { UserAvatar } from "@/components/ui/user-avatar.tsx";
|
import { CustomAvatar } from "@/components/ui/custom-avatar.tsx";
|
||||||
|
|
||||||
interface CommentDialogProps {
|
interface CommentDialogProps {
|
||||||
editor: ReturnType<typeof useEditor>;
|
editor: ReturnType<typeof useEditor>;
|
||||||
@ -94,8 +94,7 @@ function CommentDialog({ editor, pageId }: CommentDialogProps) {
|
|||||||
>
|
>
|
||||||
<Stack gap={2}>
|
<Stack gap={2}>
|
||||||
<Group>
|
<Group>
|
||||||
<UserAvatar
|
<CustomAvatar
|
||||||
color="blue"
|
|
||||||
size="sm"
|
size="sm"
|
||||||
avatarUrl={currentUser.user.avatarUrl}
|
avatarUrl={currentUser.user.avatarUrl}
|
||||||
name={currentUser.user.name}
|
name={currentUser.user.name}
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import {
|
|||||||
useUpdateCommentMutation,
|
useUpdateCommentMutation,
|
||||||
} from "@/features/comment/queries/comment-query";
|
} from "@/features/comment/queries/comment-query";
|
||||||
import { IComment } from "@/features/comment/types/comment.types";
|
import { IComment } from "@/features/comment/types/comment.types";
|
||||||
import { UserAvatar } from "@/components/ui/user-avatar";
|
import { CustomAvatar } from "@/components/ui/custom-avatar.tsx";
|
||||||
import { currentUserAtom } from "@/features/user/atoms/current-user-atom.ts";
|
import { currentUserAtom } from "@/features/user/atoms/current-user-atom.ts";
|
||||||
|
|
||||||
interface CommentListItemProps {
|
interface CommentListItemProps {
|
||||||
@ -63,8 +63,7 @@ function CommentListItem({ comment }: CommentListItemProps) {
|
|||||||
return (
|
return (
|
||||||
<Box ref={ref} pb="xs">
|
<Box ref={ref} pb="xs">
|
||||||
<Group>
|
<Group>
|
||||||
<UserAvatar
|
<CustomAvatar
|
||||||
color="blue"
|
|
||||||
size="sm"
|
size="sm"
|
||||||
avatarUrl={comment.creator.avatarUrl}
|
avatarUrl={comment.creator.avatarUrl}
|
||||||
name={comment.creator.name}
|
name={comment.creator.name}
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import { useParams } from "react-router-dom";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { IconDots } from "@tabler/icons-react";
|
import { IconDots } from "@tabler/icons-react";
|
||||||
import { modals } from "@mantine/modals";
|
import { modals } from "@mantine/modals";
|
||||||
import { UserAvatar } from "@/components/ui/user-avatar.tsx";
|
import { CustomAvatar } from "@/components/ui/custom-avatar.tsx";
|
||||||
import useUserRole from "@/hooks/use-user-role.tsx";
|
import useUserRole from "@/hooks/use-user-role.tsx";
|
||||||
|
|
||||||
export default function GroupMembersList() {
|
export default function GroupMembersList() {
|
||||||
@ -56,7 +56,7 @@ export default function GroupMembersList() {
|
|||||||
<Table.Tr key={index}>
|
<Table.Tr key={index}>
|
||||||
<Table.Td>
|
<Table.Td>
|
||||||
<Group gap="sm">
|
<Group gap="sm">
|
||||||
<UserAvatar avatarUrl={user.avatarUrl} name={user.name} />
|
<CustomAvatar avatarUrl={user.avatarUrl} name={user.name} />
|
||||||
<div>
|
<div>
|
||||||
<Text fz="sm" fw={500}>
|
<Text fz="sm" fw={500}>
|
||||||
{user.name}
|
{user.name}
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { useDebouncedValue } from "@mantine/hooks";
|
|||||||
import { useWorkspaceMembersQuery } from "@/features/workspace/queries/workspace-query.ts";
|
import { useWorkspaceMembersQuery } from "@/features/workspace/queries/workspace-query.ts";
|
||||||
import { IUser } from "@/features/user/types/user.types.ts";
|
import { IUser } from "@/features/user/types/user.types.ts";
|
||||||
import { Group, MultiSelect, MultiSelectProps, Text } from "@mantine/core";
|
import { Group, MultiSelect, MultiSelectProps, Text } from "@mantine/core";
|
||||||
import { UserAvatar } from "@/components/ui/user-avatar.tsx";
|
import { CustomAvatar } from "@/components/ui/custom-avatar.tsx";
|
||||||
|
|
||||||
interface MultiUserSelectProps {
|
interface MultiUserSelectProps {
|
||||||
onChange: (value: string[]) => void;
|
onChange: (value: string[]) => void;
|
||||||
@ -14,11 +14,10 @@ const renderMultiSelectOption: MultiSelectProps["renderOption"] = ({
|
|||||||
option,
|
option,
|
||||||
}) => (
|
}) => (
|
||||||
<Group gap="sm">
|
<Group gap="sm">
|
||||||
<UserAvatar
|
<CustomAvatar
|
||||||
avatarUrl={option?.["avatarUrl"]}
|
avatarUrl={option?.["avatarUrl"]}
|
||||||
name={option.label}
|
name={option.label}
|
||||||
size={36}
|
size={36}
|
||||||
radius="xl"
|
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<Text size="sm">{option.label}</Text>
|
<Text size="sm">{option.label}</Text>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { Text, Group, UnstyledButton } from "@mantine/core";
|
import { Text, Group, UnstyledButton } from "@mantine/core";
|
||||||
import { UserAvatar } from "@/components/ui/user-avatar";
|
import { CustomAvatar } from "@/components/ui/custom-avatar.tsx";
|
||||||
import { formattedDate } from "@/lib/time";
|
import { formattedDate } from "@/lib/time";
|
||||||
import classes from "./history.module.css";
|
import classes from "./history.module.css";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
@ -25,8 +25,7 @@ function HistoryItem({ historyItem, onSelect, isActive }: HistoryItemProps) {
|
|||||||
|
|
||||||
<div style={{ flex: 1 }}>
|
<div style={{ flex: 1 }}>
|
||||||
<Group gap={4} wrap="nowrap">
|
<Group gap={4} wrap="nowrap">
|
||||||
<UserAvatar
|
<CustomAvatar
|
||||||
color="blue"
|
|
||||||
size="sm"
|
size="sm"
|
||||||
avatarUrl={historyItem.lastUpdatedBy.avatarUrl}
|
avatarUrl={historyItem.lastUpdatedBy.avatarUrl}
|
||||||
name={historyItem.lastUpdatedBy.name}
|
name={historyItem.lastUpdatedBy.name}
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { useDebouncedValue } from "@mantine/hooks";
|
|||||||
import { Group, MultiSelect, MultiSelectProps, Text } from "@mantine/core";
|
import { Group, MultiSelect, MultiSelectProps, Text } from "@mantine/core";
|
||||||
import { IGroup } from "@/features/group/types/group.types.ts";
|
import { IGroup } from "@/features/group/types/group.types.ts";
|
||||||
import { useSearchSuggestionsQuery } from "@/features/search/queries/search-query.ts";
|
import { useSearchSuggestionsQuery } from "@/features/search/queries/search-query.ts";
|
||||||
import { UserAvatar } from "@/components/ui/user-avatar.tsx";
|
import { CustomAvatar } from "@/components/ui/custom-avatar.tsx";
|
||||||
import { IUser } from "@/features/user/types/user.types.ts";
|
import { IUser } from "@/features/user/types/user.types.ts";
|
||||||
import { IconGroupCircle } from "@/components/icons/icon-people-circle.tsx";
|
import { IconGroupCircle } from "@/components/icons/icon-people-circle.tsx";
|
||||||
|
|
||||||
@ -16,7 +16,7 @@ const renderMultiSelectOption: MultiSelectProps["renderOption"] = ({
|
|||||||
}) => (
|
}) => (
|
||||||
<Group gap="sm">
|
<Group gap="sm">
|
||||||
{option["type"] === "user" && (
|
{option["type"] === "user" && (
|
||||||
<UserAvatar
|
<CustomAvatar
|
||||||
avatarUrl={option["avatarUrl"]}
|
avatarUrl={option["avatarUrl"]}
|
||||||
size={20}
|
size={20}
|
||||||
name={option.label}
|
name={option.label}
|
||||||
|
|||||||
@ -20,9 +20,13 @@ export default function SpaceGrid() {
|
|||||||
withBorder
|
withBorder
|
||||||
>
|
>
|
||||||
<Card.Section className={classes.cardSection} h={40}></Card.Section>
|
<Card.Section className={classes.cardSection} h={40}></Card.Section>
|
||||||
<Avatar variant="filled" size="md" mt={rem(-20)}>
|
<Avatar
|
||||||
{space.name.charAt(0).toUpperCase()}
|
name={space.name}
|
||||||
</Avatar>
|
color="initials"
|
||||||
|
variant="filled"
|
||||||
|
size="md"
|
||||||
|
mt={rem(-20)}
|
||||||
|
/>
|
||||||
|
|
||||||
<Text fz="md" fw={500} mt="xs" className={classes.title}>
|
<Text fz="md" fw={500} mt="xs" className={classes.title}>
|
||||||
{space.name}
|
{space.name}
|
||||||
|
|||||||
@ -34,9 +34,11 @@ export default function SpaceList() {
|
|||||||
>
|
>
|
||||||
<Table.Td>
|
<Table.Td>
|
||||||
<Group gap="sm">
|
<Group gap="sm">
|
||||||
<Avatar color="gray" radius="xl">
|
<Avatar
|
||||||
{space.name.charAt(0).toUpperCase()}
|
color="initials"
|
||||||
</Avatar>
|
variant="filled"
|
||||||
|
name={space.name}
|
||||||
|
/>
|
||||||
<div>
|
<div>
|
||||||
<Text fz="sm" fw={500}>
|
<Text fz="sm" fw={500}>
|
||||||
{space.name}
|
{space.name}
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { useParams } from "react-router-dom";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { IconDots } from "@tabler/icons-react";
|
import { IconDots } from "@tabler/icons-react";
|
||||||
import { modals } from "@mantine/modals";
|
import { modals } from "@mantine/modals";
|
||||||
import { UserAvatar } from "@/components/ui/user-avatar.tsx";
|
import { CustomAvatar } from "@/components/ui/custom-avatar.tsx";
|
||||||
import {
|
import {
|
||||||
useChangeSpaceMemberRoleMutation,
|
useChangeSpaceMemberRoleMutation,
|
||||||
useRemoveSpaceMemberMutation,
|
useRemoveSpaceMemberMutation,
|
||||||
@ -109,7 +109,7 @@ export default function SpaceMembersList({
|
|||||||
<Table.Td>
|
<Table.Td>
|
||||||
<Group gap="sm">
|
<Group gap="sm">
|
||||||
{member.type === "user" && (
|
{member.type === "user" && (
|
||||||
<UserAvatar
|
<CustomAvatar
|
||||||
avatarUrl={member?.avatarUrl}
|
avatarUrl={member?.avatarUrl}
|
||||||
name={member.name}
|
name={member.name}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { focusAtom } from "jotai-optics";
|
|||||||
import { currentUserAtom } from "@/features/user/atoms/current-user-atom.ts";
|
import { currentUserAtom } from "@/features/user/atoms/current-user-atom.ts";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useAtom } from "jotai";
|
import { useAtom } from "jotai";
|
||||||
import { UserAvatar } from "@/components/ui/user-avatar.tsx";
|
import { CustomAvatar } from "@/components/ui/custom-avatar.tsx";
|
||||||
import { FileButton, Tooltip } from "@mantine/core";
|
import { FileButton, Tooltip } from "@mantine/core";
|
||||||
import { uploadAvatar } from "@/features/user/services/user-service.ts";
|
import { uploadAvatar } from "@/features/user/services/user-service.ts";
|
||||||
|
|
||||||
@ -37,10 +37,9 @@ export default function AccountAvatar() {
|
|||||||
<FileButton onChange={handleFileChange} accept="image/png,image/jpeg">
|
<FileButton onChange={handleFileChange} accept="image/png,image/jpeg">
|
||||||
{(props) => (
|
{(props) => (
|
||||||
<Tooltip label="Change photo" position="bottom">
|
<Tooltip label="Change photo" position="bottom">
|
||||||
<UserAvatar
|
<CustomAvatar
|
||||||
{...props}
|
{...props}
|
||||||
component="button"
|
component="button"
|
||||||
radius="xl"
|
|
||||||
size="60px"
|
size="60px"
|
||||||
avatarUrl={currentUser?.user.avatarUrl}
|
avatarUrl={currentUser?.user.avatarUrl}
|
||||||
name={currentUser?.user.name}
|
name={currentUser?.user.name}
|
||||||
|
|||||||
@ -35,7 +35,7 @@ export default function WorkspaceInvitesTable() {
|
|||||||
<Table.Tr key={index}>
|
<Table.Tr key={index}>
|
||||||
<Table.Td>
|
<Table.Td>
|
||||||
<Group gap="sm">
|
<Group gap="sm">
|
||||||
<Avatar src={invitation.email} />
|
<Avatar name={invitation.email} color="initials" />
|
||||||
<div>
|
<div>
|
||||||
<Text fz="sm" fw={500}>
|
<Text fz="sm" fw={500}>
|
||||||
{invitation.email}
|
{invitation.email}
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import {
|
|||||||
useChangeMemberRoleMutation,
|
useChangeMemberRoleMutation,
|
||||||
useWorkspaceMembersQuery,
|
useWorkspaceMembersQuery,
|
||||||
} from "@/features/workspace/queries/workspace-query.ts";
|
} from "@/features/workspace/queries/workspace-query.ts";
|
||||||
import { UserAvatar } from "@/components/ui/user-avatar.tsx";
|
import { CustomAvatar } from "@/components/ui/custom-avatar.tsx";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import RoleSelectMenu from "@/components/ui/role-select-menu.tsx";
|
import RoleSelectMenu from "@/components/ui/role-select-menu.tsx";
|
||||||
import {
|
import {
|
||||||
@ -51,7 +51,7 @@ export default function WorkspaceMembersTable() {
|
|||||||
<Table.Tr key={index}>
|
<Table.Tr key={index}>
|
||||||
<Table.Td>
|
<Table.Td>
|
||||||
<Group gap="sm">
|
<Group gap="sm">
|
||||||
<UserAvatar avatarUrl={user.avatarUrl} name={user.name} />
|
<CustomAvatar avatarUrl={user.avatarUrl} name={user.name} />
|
||||||
<div>
|
<div>
|
||||||
<Text fz="sm" fw={500}>
|
<Text fz="sm" fw={500}>
|
||||||
{user.name}
|
{user.name}
|
||||||
|
|||||||
Reference in New Issue
Block a user