mirror of
https://github.com/hexastack/hexabot
synced 2025-06-26 18:27:28 +00:00
feat: add AvatarInput to update own avatar
This commit is contained in:
parent
3721f4365e
commit
355c6ebe26
@ -532,6 +532,7 @@
|
||||
"invite": "Invite",
|
||||
"send": "Send",
|
||||
"fields": "Fields",
|
||||
"upload": "Upload",
|
||||
"import": "Import",
|
||||
"export": "Export",
|
||||
"manage": "Manage",
|
||||
|
@ -533,6 +533,7 @@
|
||||
"invite": "Inviter",
|
||||
"send": "Envoyer",
|
||||
"fields": "Champs",
|
||||
"upload": "Télécharger",
|
||||
"import": "Import",
|
||||
"export": "Export",
|
||||
"manage": "Gérer",
|
||||
|
89
frontend/src/app-components/inputs/AvatarInput.tsx
Normal file
89
frontend/src/app-components/inputs/AvatarInput.tsx
Normal file
@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright © 2025 Hexastack. All rights reserved.
|
||||
*
|
||||
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
||||
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
||||
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
|
||||
*/
|
||||
|
||||
|
||||
import { Avatar, Box, FormHelperText, FormLabel } from "@mui/material";
|
||||
import { forwardRef, useState } from "react";
|
||||
|
||||
import { getAvatarSrc } from "@/components/inbox/helpers/mapMessages";
|
||||
import { useAuth } from "@/hooks/useAuth";
|
||||
import { useConfig } from "@/hooks/useConfig";
|
||||
import { useTranslate } from "@/hooks/useTranslate";
|
||||
import { theme } from "@/layout/themes/theme";
|
||||
import { EntityType } from "@/services/types";
|
||||
|
||||
import FileUploadButton from "./FileInput";
|
||||
|
||||
type AvatarInputProps = {
|
||||
label: string;
|
||||
value: File | undefined | null;
|
||||
accept: string;
|
||||
size: number;
|
||||
onChange: (file: File) => void;
|
||||
error?: boolean;
|
||||
helperText?: string;
|
||||
};
|
||||
|
||||
const AvatarInput = forwardRef<HTMLDivElement, AvatarInputProps>(
|
||||
({ label, accept, size, onChange, error, helperText }, ref) => {
|
||||
const { apiUrl } = useConfig();
|
||||
const { user } = useAuth();
|
||||
const [avatarSrc, setAvatarSrc] = useState(
|
||||
getAvatarSrc(apiUrl, EntityType.USER, user?.id),
|
||||
);
|
||||
const { t } = useTranslate();
|
||||
const handleChange = (file: File) => {
|
||||
onChange(file);
|
||||
setAvatarSrc(URL.createObjectURL(file));
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
ref={ref}
|
||||
sx={{
|
||||
position: "relative",
|
||||
}}
|
||||
>
|
||||
<FormLabel
|
||||
component="h2"
|
||||
style={{ display: "inline-block", marginBottom: 1 }}
|
||||
>
|
||||
{label}
|
||||
</FormLabel>
|
||||
<Avatar
|
||||
src={avatarSrc}
|
||||
color={theme.palette.text.secondary}
|
||||
sx={{ width: size, height: size, margin: "auto" }}
|
||||
variant="rounded"
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
right: "50%",
|
||||
bottom: "1rem",
|
||||
transform: "translateX(50%)",
|
||||
}}
|
||||
>
|
||||
<FileUploadButton
|
||||
accept={accept}
|
||||
label={t("button.upload")}
|
||||
onChange={handleChange}
|
||||
isLoading={false}
|
||||
/>
|
||||
</Box>
|
||||
{helperText ? (
|
||||
<FormHelperText error={error}>{helperText}</FormHelperText>
|
||||
) : null}
|
||||
</Box>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
AvatarInput.displayName = "AttachmentInput";
|
||||
|
||||
export default AvatarInput;
|
@ -1,13 +1,13 @@
|
||||
/*
|
||||
* Copyright © 2024 Hexastack. All rights reserved.
|
||||
* Copyright © 2025 Hexastack. All rights reserved.
|
||||
*
|
||||
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
||||
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
||||
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
|
||||
*/
|
||||
|
||||
|
||||
import CheckIcon from "@mui/icons-material/Check";
|
||||
import DeleteIcon from "@mui/icons-material/Delete";
|
||||
import EmailIcon from "@mui/icons-material/Email";
|
||||
import KeyIcon from "@mui/icons-material/Key";
|
||||
import LanguageIcon from "@mui/icons-material/Language";
|
||||
@ -16,10 +16,10 @@ import { FC } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { useQueryClient } from "react-query";
|
||||
|
||||
import AttachmentInput from "@/app-components/attachment/AttachmentInput";
|
||||
import { ContentItem } from "@/app-components/dialogs";
|
||||
import { ContentContainer } from "@/app-components/dialogs/layouts/ContentContainer";
|
||||
import { Adornment } from "@/app-components/inputs/Adornment";
|
||||
import AvatarInput from "@/app-components/inputs/AvatarInput";
|
||||
import { Input } from "@/app-components/inputs/Input";
|
||||
import { PasswordInput } from "@/app-components/inputs/PasswordInput";
|
||||
import { useUpdateProfile } from "@/hooks/entities/auth-hooks";
|
||||
@ -27,11 +27,9 @@ import { CURRENT_USER_KEY } from "@/hooks/useAuth";
|
||||
import { useToast } from "@/hooks/useToast";
|
||||
import { useTranslate } from "@/hooks/useTranslate";
|
||||
import { useValidationRules } from "@/hooks/useValidationRules";
|
||||
import { IUser, IUserAttributes } from "@/types/user.types";
|
||||
import { IProfileAttributes, IUser } from "@/types/user.types";
|
||||
import { MIME_TYPES } from "@/utils/attachment";
|
||||
|
||||
type TUserProfileExtendedPayload = IUserAttributes & { password2: string };
|
||||
|
||||
type ProfileFormProps = { user: IUser };
|
||||
|
||||
export const ProfileForm: FC<ProfileFormProps> = ({ user }) => {
|
||||
@ -55,14 +53,12 @@ export const ProfileForm: FC<ProfileFormProps> = ({ user }) => {
|
||||
formState: { errors },
|
||||
register,
|
||||
setValue,
|
||||
getValues,
|
||||
} = useForm<TUserProfileExtendedPayload>({
|
||||
} = useForm<IProfileAttributes>({
|
||||
defaultValues: {
|
||||
first_name: user.first_name,
|
||||
last_name: user.last_name,
|
||||
email: user.email,
|
||||
language: user.language,
|
||||
avatar: user.avatar,
|
||||
},
|
||||
});
|
||||
const rules = useValidationRules();
|
||||
@ -89,7 +85,7 @@ export const ProfileForm: FC<ProfileFormProps> = ({ user }) => {
|
||||
password,
|
||||
password2: _password2,
|
||||
...rest
|
||||
}: TUserProfileExtendedPayload) => {
|
||||
}: IProfileAttributes) => {
|
||||
await updateProfile({
|
||||
...rest,
|
||||
password: password || undefined,
|
||||
@ -106,32 +102,13 @@ export const ProfileForm: FC<ProfileFormProps> = ({ user }) => {
|
||||
render={({ field }) => (
|
||||
<>
|
||||
<Box sx={{ position: "relative" }}>
|
||||
<AttachmentInput
|
||||
<AvatarInput
|
||||
label={t("label.avatar")}
|
||||
format="small"
|
||||
accept={MIME_TYPES["images"].join(",")}
|
||||
enableMediaLibrary={false}
|
||||
size={256}
|
||||
{...field}
|
||||
onChange={(attachment) => setValue("avatar", attachment)}
|
||||
onChange={(file) => setValue("avatar", file)}
|
||||
/>
|
||||
{getValues("avatar") ? (
|
||||
<Button
|
||||
startIcon={<DeleteIcon />}
|
||||
onClick={() => setValue("avatar", null)}
|
||||
color="error"
|
||||
variant="contained"
|
||||
size="small"
|
||||
sx={{
|
||||
position: "absolute",
|
||||
right: "50%",
|
||||
bottom: "1rem",
|
||||
transform: "translateX(50%)",
|
||||
}}
|
||||
>
|
||||
{t("button.remove")}
|
||||
</Button>
|
||||
) : null}
|
||||
</Box>
|
||||
<Typography
|
||||
variant="body2"
|
||||
|
@ -1,17 +1,23 @@
|
||||
/*
|
||||
* Copyright © 2024 Hexastack. All rights reserved.
|
||||
* Copyright © 2025 Hexastack. All rights reserved.
|
||||
*
|
||||
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
||||
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
||||
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
|
||||
*/
|
||||
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { useMutation, useQuery, useQueryClient } from "react-query";
|
||||
|
||||
import { EntityType, TMutationOptions } from "@/services/types";
|
||||
import { ILoginAttributes } from "@/types/auth/login.types";
|
||||
import { IUser, IUserAttributes, IUserStub } from "@/types/user.types";
|
||||
import {
|
||||
IProfileAttributes,
|
||||
IUser,
|
||||
IUserAttributes,
|
||||
IUserStub,
|
||||
} from "@/types/user.types";
|
||||
import { useSocket } from "@/websocket/socket-hooks";
|
||||
|
||||
import { useFind } from "../crud/useFind";
|
||||
@ -156,7 +162,7 @@ export const useLoadSettings = () => {
|
||||
|
||||
export const useUpdateProfile = (
|
||||
options?: Omit<
|
||||
TMutationOptions<IUserStub, Error, Partial<IUserAttributes>>,
|
||||
TMutationOptions<IUserStub, Error, Partial<IProfileAttributes>>,
|
||||
"mutationFn"
|
||||
>,
|
||||
) => {
|
||||
|
@ -1,11 +1,12 @@
|
||||
/*
|
||||
* Copyright © 2024 Hexastack. All rights reserved.
|
||||
* Copyright © 2025 Hexastack. All rights reserved.
|
||||
*
|
||||
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
||||
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
||||
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
|
||||
*/
|
||||
|
||||
|
||||
import { AxiosInstance, AxiosResponse } from "axios";
|
||||
|
||||
import { ILoginAttributes } from "@/types/auth/login.types";
|
||||
@ -15,7 +16,12 @@ import { ICsrf } from "@/types/csrf.types";
|
||||
import { IInvitation, IInvitationAttributes } from "@/types/invitation.types";
|
||||
import { INlpDatasetSampleAttributes } from "@/types/nlp-sample.types";
|
||||
import { IResetPayload, IResetRequest } from "@/types/reset.types";
|
||||
import { IUser, IUserAttributes, IUserStub } from "@/types/user.types";
|
||||
import {
|
||||
IProfileAttributes,
|
||||
IUser,
|
||||
IUserAttributes,
|
||||
IUserStub,
|
||||
} from "@/types/user.types";
|
||||
|
||||
import { EntityType, Format, TCount, TypeByFormat } from "./types";
|
||||
|
||||
@ -100,15 +106,27 @@ export class ApiClient {
|
||||
return data;
|
||||
}
|
||||
|
||||
async updateProfile(id: string, payload: Partial<IUserAttributes>) {
|
||||
async updateProfile(id: string, payload: Partial<IProfileAttributes>) {
|
||||
const { _csrf } = await this.getCsrf();
|
||||
const formData = new FormData();
|
||||
|
||||
for (const [key, value] of Object.entries(payload)) {
|
||||
if (value !== undefined) {
|
||||
formData.append(key, value as string | Blob);
|
||||
}
|
||||
}
|
||||
|
||||
// Append the CSRF token
|
||||
formData.append("_csrf", _csrf);
|
||||
|
||||
const { data } = await this.request.patch<
|
||||
IUserStub,
|
||||
AxiosResponse<IUserStub>,
|
||||
Partial<IUserAttributes> & ICsrf
|
||||
>(`${ROUTES.PROFILE}/${id}`, {
|
||||
...payload,
|
||||
_csrf,
|
||||
Partial<IProfileAttributes>
|
||||
>(`${ROUTES.PROFILE}/${id}?_csrf=${_csrf}`, payload, {
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data",
|
||||
},
|
||||
});
|
||||
|
||||
return data;
|
||||
|
@ -1,11 +1,12 @@
|
||||
/*
|
||||
* Copyright © 2024 Hexastack. All rights reserved.
|
||||
* Copyright © 2025 Hexastack. All rights reserved.
|
||||
*
|
||||
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
||||
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
||||
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
|
||||
*/
|
||||
|
||||
|
||||
import { EntityType, Format } from "@/services/types";
|
||||
|
||||
import { IAttachment } from "./attachment.types";
|
||||
@ -27,6 +28,11 @@ export interface IUserStub
|
||||
extends IBaseSchema,
|
||||
OmitPopulate<IUserAttributes, EntityType.USER> {}
|
||||
|
||||
export interface IProfileAttributes extends Partial<IUserStub> {
|
||||
password2?: string;
|
||||
avatar?: File | null;
|
||||
}
|
||||
|
||||
export interface IUser extends IUserStub, IFormat<Format.BASIC> {
|
||||
roles: string[]; //populated by default
|
||||
avatar: string | null;
|
||||
|
Loading…
Reference in New Issue
Block a user