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.Security.Claims;
using System.Threading.Tasks;
using Sozsoft.Platform.BlobStoring;
using Sozsoft.Platform.Entities;
using Sozsoft.Platform.Extensions;
using Sozsoft.Platform.Identity.Dto;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using OpenIddict.Abstractions;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;
@ -32,6 +34,7 @@ public class PlatformIdentityAppService : ApplicationService
public IRepository<WorkHour, Guid> workHourRepository { get; }
public IRepository<Department, Guid> departmentRepository { get; }
public IRepository<JobPosition, Guid> jobPositionRepository { get; }
public BlobManager BlobCdnManager { get; }
public PlatformIdentityAppService(
IIdentityUserAppService identityUserAppService,
@ -45,6 +48,7 @@ public class PlatformIdentityAppService : ApplicationService
IRepository<WorkHour, Guid> workHourRepository,
IRepository<Department, Guid> departmentRepository,
IRepository<JobPosition, Guid> jobPositionRepository,
BlobManager blobCdnManager,
IGuidGenerator guidGenerator
)
{
@ -55,6 +59,7 @@ public class PlatformIdentityAppService : ApplicationService
this.workHourRepository = workHourRepository;
this.departmentRepository = departmentRepository;
this.jobPositionRepository = jobPositionRepository;
this.BlobCdnManager = blobCdnManager;
this.permissionRepository = permissionRepository;
this.branchRepository = branchRepository;
this.branchUsersRepository = branchUsersRepository;
@ -273,6 +278,22 @@ public class PlatformIdentityAppService : ApplicationService
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()
{
var list = await permissionRepository.GetListAsync();

View file

@ -11,6 +11,11 @@ import { ListResultDto, PagedAndSortedResultRequestDto, PagedResultDto } from '.
import { AuditLogDto } from '../proxy/auditLog/audit-log'
import apiService from './api.service'
export interface UserAvatarUpdateInput {
userId: string
avatar?: File
}
export const getRoles = (skipCount = 0, maxResultCount = 10) =>
apiService.fetchData<ListResultDto<IdentityRoleDto>>({
method: 'GET',
@ -36,6 +41,21 @@ export const putUserDetail = (input: UserInfoViewModel) =>
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) =>
apiService.fetchData({
method: 'PUT',

View file

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