Kullanıcı Detayları ve Avatar

This commit is contained in:
Sedat Öztürk 2026-06-07 23:45:39 +03:00
parent 12f046f262
commit 233c9b7502
4 changed files with 111 additions and 23 deletions

View file

@ -0,0 +1,11 @@
using System;
using Volo.Abp.Content;
namespace Sozsoft.Platform.Identity.Dto;
public class UserAvatarUpdateInput
{
public Guid UserId { get; set; }
public IRemoteStreamContent Avatar { get; set; }
}

View file

@ -3,10 +3,12 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Security.Claims; using System.Security.Claims;
using System.Threading.Tasks; using System.Threading.Tasks;
using Sozsoft.Platform.BlobStoring;
using Sozsoft.Platform.Entities; using Sozsoft.Platform.Entities;
using Sozsoft.Platform.Extensions; using Sozsoft.Platform.Extensions;
using Sozsoft.Platform.Identity.Dto; using Sozsoft.Platform.Identity.Dto;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using OpenIddict.Abstractions; using OpenIddict.Abstractions;
using Volo.Abp.Application.Services; using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories; using Volo.Abp.Domain.Repositories;
@ -32,6 +34,7 @@ public class PlatformIdentityAppService : ApplicationService
public IRepository<WorkHour, Guid> workHourRepository { get; } public IRepository<WorkHour, Guid> workHourRepository { get; }
public IRepository<Department, Guid> departmentRepository { get; } public IRepository<Department, Guid> departmentRepository { get; }
public IRepository<JobPosition, Guid> jobPositionRepository { get; } public IRepository<JobPosition, Guid> jobPositionRepository { get; }
public BlobManager BlobCdnManager { get; }
public PlatformIdentityAppService( public PlatformIdentityAppService(
IIdentityUserAppService identityUserAppService, IIdentityUserAppService identityUserAppService,
@ -45,6 +48,7 @@ public class PlatformIdentityAppService : ApplicationService
IRepository<WorkHour, Guid> workHourRepository, IRepository<WorkHour, Guid> workHourRepository,
IRepository<Department, Guid> departmentRepository, IRepository<Department, Guid> departmentRepository,
IRepository<JobPosition, Guid> jobPositionRepository, IRepository<JobPosition, Guid> jobPositionRepository,
BlobManager blobCdnManager,
IGuidGenerator guidGenerator IGuidGenerator guidGenerator
) )
{ {
@ -55,6 +59,7 @@ public class PlatformIdentityAppService : ApplicationService
this.workHourRepository = workHourRepository; this.workHourRepository = workHourRepository;
this.departmentRepository = departmentRepository; this.departmentRepository = departmentRepository;
this.jobPositionRepository = jobPositionRepository; this.jobPositionRepository = jobPositionRepository;
this.BlobCdnManager = blobCdnManager;
this.permissionRepository = permissionRepository; this.permissionRepository = permissionRepository;
this.branchRepository = branchRepository; this.branchRepository = branchRepository;
this.branchUsersRepository = branchUsersRepository; this.branchUsersRepository = branchUsersRepository;
@ -273,6 +278,22 @@ public class PlatformIdentityAppService : ApplicationService
await UserManager.UpdateAsync(user); await UserManager.UpdateAsync(user);
} }
[Authorize(IdentityPermissions.Users.Update)]
public async Task UpdateAvatarAsync([FromForm] UserAvatarUpdateInput input)
{
var user = await UserManager.GetByIdAsync(input.UserId);
var fileName = $"{user.Id}.jpg";
if (input.Avatar is null || input.Avatar.ContentLength == 0)
{
await BlobCdnManager.DeleteAsync(BlobContainerNames.Avatar, fileName);
}
else
{
await BlobCdnManager.SaveAsync(BlobContainerNames.Avatar, fileName, input.Avatar.GetStream());
}
}
public async Task<List<PermissionDefinitionRecord>> GetPermissionList() public async Task<List<PermissionDefinitionRecord>> GetPermissionList()
{ {
var list = await permissionRepository.GetListAsync(); var list = await permissionRepository.GetListAsync();

View file

@ -11,6 +11,11 @@ import { ListResultDto, PagedAndSortedResultRequestDto, PagedResultDto } from '.
import { AuditLogDto } from '../proxy/auditLog/audit-log' import { AuditLogDto } from '../proxy/auditLog/audit-log'
import apiService from './api.service' import apiService from './api.service'
export interface UserAvatarUpdateInput {
userId: string
avatar?: File
}
export const getRoles = (skipCount = 0, maxResultCount = 10) => export const getRoles = (skipCount = 0, maxResultCount = 10) =>
apiService.fetchData<ListResultDto<IdentityRoleDto>>({ apiService.fetchData<ListResultDto<IdentityRoleDto>>({
method: 'GET', method: 'GET',
@ -36,6 +41,21 @@ export const putUserDetail = (input: UserInfoViewModel) =>
data: input, data: input,
}) })
export const putUserAvatar = (input: UserAvatarUpdateInput) => {
const formData = new FormData()
formData.append('userId', input.userId)
if (input.avatar) {
formData.append('avatar', input.avatar)
}
return apiService.fetchData({
method: 'PUT',
url: `/api/app/platform-identity/avatar`,
data: formData,
})
}
export const putUserLookout = (input: UserInfoViewModel) => export const putUserLookout = (input: UserInfoViewModel) =>
apiService.fetchData({ apiService.fetchData({
method: 'PUT', method: 'PUT',

View file

@ -26,11 +26,11 @@ import {
deleteClaimUser, deleteClaimUser,
getUserDetail, getUserDetail,
postClaimUser, postClaimUser,
putUserAvatar,
putUserDetail, putUserDetail,
putUserLookout, putUserLookout,
putUserPermission, putUserPermission,
} from '@/services/identity.service' } from '@/services/identity.service'
import { updateProfile } from '@/services/account.service'
import { CountryDto, getCountry } from '@/services/home.service' import { CountryDto, getCountry } from '@/services/home.service'
import { useLocalization } from '@/utils/hooks/useLocalization' import { useLocalization } from '@/utils/hooks/useLocalization'
import dayjs from 'dayjs' import dayjs from 'dayjs'
@ -97,6 +97,7 @@ function UserDetails() {
const [confirmDeleteClaim, setConfirmDeleteClaim] = useState<AssignedClaimViewModel | null>(null) const [confirmDeleteClaim, setConfirmDeleteClaim] = useState<AssignedClaimViewModel | null>(null)
const [countries, setCountries] = useState<CountryDto[]>([]) const [countries, setCountries] = useState<CountryDto[]>([])
const [image, setImage] = useState<string | undefined>() const [image, setImage] = useState<string | undefined>()
const [hasAvatarChange, setHasAvatarChange] = useState(false)
const cropperRef = useRef<CropperRef>(null) const cropperRef = useRef<CropperRef>(null)
const previewRef = useRef<CropperPreviewRef>(null) const previewRef = useRef<CropperPreviewRef>(null)
const auth = useStoreState((state) => state.auth) const auth = useStoreState((state) => state.auth)
@ -111,15 +112,26 @@ function UserDetails() {
const isTwoFactorEnabled = setting('Abp.Account.TwoFactor.Enabled') const isTwoFactorEnabled = setting('Abp.Account.TwoFactor.Enabled')
const getUser = async (syncAvatar = true) => { const getUser = async (syncAvatar = true) => {
const { data } = await getUserDetail(userId || '') if (!userId) {
return
}
const { data } = await getUserDetail(userId)
setUserDetails(data) setUserDetails(data)
if (syncAvatar) { if (syncAvatar) {
setImage(`${AVATAR_URL(data.id, data.tenantId)}?${dayjs().unix()}`) setImage(`${AVATAR_URL(data.id, data.tenantId)}?${dayjs().unix()}`)
setHasAvatarChange(false)
} }
} }
useEffect(() => { useEffect(() => {
setUserDetails(undefined)
setImage(undefined)
setHasAvatarChange(false)
getUser() getUser()
}, [userId])
useEffect(() => {
getCountry().then(({ data }) => setCountries(data)) getCountry().then(({ data }) => setCountries(data))
}, []) }, [])
@ -131,8 +143,10 @@ function UserDetails() {
const onChooseImage = async (file: File[]) => { const onChooseImage = async (file: File[]) => {
if (file[0]) { if (file[0]) {
setImage(URL.createObjectURL(file[0])) setImage(URL.createObjectURL(file[0]))
setHasAvatarChange(true)
} else { } else {
setImage(undefined) setImage(undefined)
setHasAvatarChange(true)
} }
} }
@ -246,34 +260,49 @@ function UserDetails() {
<TabContent value="user"> <TabContent value="user">
<div className="px-4 py-6"> <div className="px-4 py-6">
<Formik <Formik
enableReinitialize
initialValues={userDetails} initialValues={userDetails}
onSubmit={async (values, { resetForm, setSubmitting }) => { onSubmit={async (values, { resetForm, setSubmitting }) => {
setSubmitting(true) setSubmitting(true)
await putUserDetail({ ...values }) await putUserDetail({ ...values })
let keepCurrentAvatar = false if (values.id === auth.user.id) {
const avatar = await getCroppedAvatar()
const resp = await updateProfile({
name: values.name ?? '',
surname: values.surname ?? '',
avatar: avatar ? new File([avatar], 'avatar') : undefined,
})
if (resp.status === 200) {
const avatarUrl =
AVATAR_URL(auth.user.id, auth.tenant?.tenantId) + `?${dayjs().unix()}`
setUser({ setUser({
...auth.user, ...auth.user,
name: `${resp.data.name} ${resp.data.surname}`.trim(), name: `${values.name ?? ''} ${values.surname ?? ''}`.trim(),
avatar: avatarUrl,
}) })
setImage(avatarUrl) }
keepCurrentAvatar = true let keepCurrentAvatar = false
} else {
toast.push(<Notification title={resp?.error?.message} type="danger" />, { if (hasAvatarChange) {
placement: 'top-end', const avatar = await getCroppedAvatar()
const resp = await putUserAvatar({
userId: values.id!,
avatar: avatar ? new File([avatar], 'avatar') : undefined,
}) })
if (resp.status === 200) {
const avatarUrl =
AVATAR_URL(values.id, values.tenantId) + `?${dayjs().unix()}`
if (values.id === auth.user.id) {
setUser({
...auth.user,
name: `${values.name ?? ''} ${values.surname ?? ''}`.trim(),
avatar: avatarUrl,
})
}
setImage(avatarUrl)
setHasAvatarChange(false)
keepCurrentAvatar = true
} else {
const errorMessage =
(resp as { error?: { message?: string } })?.error?.message || 'Hata'
toast.push(<Notification title={errorMessage} type="danger" />, {
placement: 'top-end',
})
}
} }
toast.push( toast.push(
@ -542,7 +571,10 @@ function UserDetails() {
<Button <Button
type="button" type="button"
icon={<FaTrashAlt />} icon={<FaTrashAlt />}
onClick={() => setImage(undefined)} onClick={() => {
setImage(undefined)
setHasAvatarChange(true)
}}
></Button> ></Button>
</div> </div>
{image && ( {image && (
@ -574,6 +606,7 @@ function UserDetails() {
<TabContent value="permission"> <TabContent value="permission">
<div className="px-4 py-6"> <div className="px-4 py-6">
<Formik <Formik
enableReinitialize
initialValues={userDetails} initialValues={userDetails}
onSubmit={async (values, { setSubmitting }) => { onSubmit={async (values, { setSubmitting }) => {
setSubmitting(true) setSubmitting(true)
@ -685,6 +718,7 @@ function UserDetails() {
<TabContent value="work"> <TabContent value="work">
<div className="px-4 py-6"> <div className="px-4 py-6">
<Formik <Formik
enableReinitialize
initialValues={userDetails} initialValues={userDetails}
onSubmit={async (values, { resetForm, setSubmitting }) => { onSubmit={async (values, { resetForm, setSubmitting }) => {
setSubmitting(true) setSubmitting(true)
@ -866,6 +900,7 @@ function UserDetails() {
<TabContent value="identity"> <TabContent value="identity">
<div className="px-4 py-6"> <div className="px-4 py-6">
<Formik <Formik
enableReinitialize
initialValues={userDetails} initialValues={userDetails}
onSubmit={async (values, { resetForm, setSubmitting }) => { onSubmit={async (values, { resetForm, setSubmitting }) => {
setSubmitting(true) setSubmitting(true)
@ -1222,6 +1257,7 @@ function UserDetails() {
<TabContent value="lockout"> <TabContent value="lockout">
<div className="px-4 py-6"> <div className="px-4 py-6">
<Formik <Formik
enableReinitialize
initialValues={userDetails} initialValues={userDetails}
onSubmit={async (values, { setSubmitting }) => { onSubmit={async (values, { setSubmitting }) => {
setSubmitting(true) setSubmitting(true)