Bổ sung chức năng cập nhật hình ảnh cho Profiles
This commit is contained in:
parent
2941663e7b
commit
80bc1de1a3
|
|
@ -3,12 +3,14 @@
|
||||||
namespace Modules\Admin\app\Http\Controllers;
|
namespace Modules\Admin\app\Http\Controllers;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\User;
|
||||||
use App\Services\JiraService;
|
use App\Services\JiraService;
|
||||||
use App\Traits\AnalyzeData;
|
use App\Traits\AnalyzeData;
|
||||||
use App\Traits\HasFilterRequest;
|
use App\Traits\HasFilterRequest;
|
||||||
use App\Traits\HasOrderByRequest;
|
use App\Traits\HasOrderByRequest;
|
||||||
use App\Traits\HasSearchRequest;
|
use App\Traits\HasSearchRequest;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Modules\Admin\app\Models\Sprint;
|
use Modules\Admin\app\Models\Sprint;
|
||||||
use Modules\Admin\app\Models\UserCriteria;
|
use Modules\Admin\app\Models\UserCriteria;
|
||||||
|
|
||||||
|
|
@ -72,4 +74,23 @@ class ProfileController extends Controller
|
||||||
});
|
});
|
||||||
return array_values($filteredProjects) ? array_values($filteredProjects)[0] : array_values($filteredProjects);
|
return array_values($filteredProjects) ? array_values($filteredProjects)[0] : array_values($filteredProjects);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function updateProfilesData(Request $request)
|
||||||
|
{
|
||||||
|
$userInfo = auth('admins')->user();
|
||||||
|
$request->validate([
|
||||||
|
'file' => 'required|image|mimes:jpeg,png,jpg,gif|max:2048', // Chỉ chấp nhận file ảnh dưới 2MB
|
||||||
|
]);
|
||||||
|
|
||||||
|
$user = User::findOrFail($userInfo->id);
|
||||||
|
|
||||||
|
if ($user->avatar) {
|
||||||
|
Storage::disk('public')->delete($user->avatar);
|
||||||
|
}
|
||||||
|
|
||||||
|
$path = $request->file('file')->store('avatars', 'public');
|
||||||
|
$user->avatar = $path;
|
||||||
|
$user->save();
|
||||||
|
return AbstractController::ResultSuccess($path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -73,3 +73,4 @@ export const createTestCase = API_URL + 'v1/admin/criterias/test-cases'
|
||||||
|
|
||||||
//Profile
|
//Profile
|
||||||
export const getProfilesData = API_URL + 'v1/admin/criterias/profiles-data'
|
export const getProfilesData = API_URL + 'v1/admin/criterias/profiles-data'
|
||||||
|
export const updateProfilesData = API_URL + 'v1/admin/criterias/profiles-data/update'
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import { logout } from '@/rtk/dispatches/auth'
|
||||||
import { get, post } from '@/rtk/helpers/apiService'
|
import { get, post } from '@/rtk/helpers/apiService'
|
||||||
import { requirementsPassword } from '@/rtk/helpers/variables'
|
import { requirementsPassword } from '@/rtk/helpers/variables'
|
||||||
import { useAppDispatch, useAppSelector } from '@/rtk/hooks'
|
import { useAppDispatch, useAppSelector } from '@/rtk/hooks'
|
||||||
|
import { getUser } from '@/rtk/localStorage'
|
||||||
import {
|
import {
|
||||||
Avatar,
|
Avatar,
|
||||||
Box,
|
Box,
|
||||||
|
|
@ -130,7 +131,6 @@ const Navbar = ({
|
||||||
}: TProps) => {
|
}: TProps) => {
|
||||||
const user = useAppSelector((state) => state.authentication.user)
|
const user = useAppSelector((state) => state.authentication.user)
|
||||||
const [active, setActive] = useState<string>('')
|
const [active, setActive] = useState<string>('')
|
||||||
|
|
||||||
//state from dashboard
|
//state from dashboard
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -151,6 +151,8 @@ const Navbar = ({
|
||||||
confirm_password: '',
|
confirm_password: '',
|
||||||
})
|
})
|
||||||
const [countSpam, setCountSpam] = useState(0)
|
const [countSpam, setCountSpam] = useState(0)
|
||||||
|
const [avatar, setAvatar] = useState(user.user.avatar)
|
||||||
|
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
const passwordRegex =
|
const passwordRegex =
|
||||||
|
|
@ -161,6 +163,10 @@ const Navbar = ({
|
||||||
getInitialValueInEffect: true,
|
getInitialValueInEffect: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setAvatar(user.user.avatar)
|
||||||
|
}, [user])
|
||||||
|
|
||||||
// const links = data.map((item) => {
|
// const links = data.map((item) => {
|
||||||
// if (
|
// if (
|
||||||
// user?.user?.permission !== 'admin' &&
|
// user?.user?.permission !== 'admin' &&
|
||||||
|
|
@ -362,7 +368,11 @@ const Navbar = ({
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Avatar
|
<Avatar
|
||||||
src={user.user.avatarUrl || ''}
|
src={
|
||||||
|
avatar !== ''
|
||||||
|
? import.meta.env.VITE_BACKEND_URL + 'storage/' + avatar
|
||||||
|
: ''
|
||||||
|
}
|
||||||
alt="User Avatar"
|
alt="User Avatar"
|
||||||
size={60}
|
size={60}
|
||||||
radius="xl"
|
radius="xl"
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
import { getProfilesData } from '@/api/Admin'
|
import { getProfilesData, updateProfilesData } from '@/api/Admin'
|
||||||
import { changePassword } from '@/api/Auth'
|
import { changePassword } from '@/api/Auth'
|
||||||
import PasswordRequirementInput from '@/components/PasswordRequirementInput/PasswordRequirementInput'
|
import PasswordRequirementInput from '@/components/PasswordRequirementInput/PasswordRequirementInput'
|
||||||
import { logout } from '@/rtk/dispatches/auth'
|
import { logout } from '@/rtk/dispatches/auth'
|
||||||
import { get, post } from '@/rtk/helpers/apiService'
|
import { get, post, postImage } from '@/rtk/helpers/apiService'
|
||||||
import { requirementsPassword } from '@/rtk/helpers/variables'
|
import { requirementsPassword } from '@/rtk/helpers/variables'
|
||||||
import { useAppDispatch, useAppSelector } from '@/rtk/hooks'
|
import { useAppDispatch, useAppSelector } from '@/rtk/hooks'
|
||||||
|
import { getUser } from '@/rtk/localStorage'
|
||||||
import {
|
import {
|
||||||
Avatar,
|
Avatar,
|
||||||
Box,
|
Box,
|
||||||
|
|
@ -21,6 +22,7 @@ import { IconCornerDownRight, IconPasswordUser } from '@tabler/icons-react'
|
||||||
import { useCallback, useEffect, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import classes from './Profile.module.css'
|
import classes from './Profile.module.css'
|
||||||
|
import { success } from '@/rtk/slices/auth'
|
||||||
|
|
||||||
interface TableRow {
|
interface TableRow {
|
||||||
// Add properties for each column in the table
|
// Add properties for each column in the table
|
||||||
|
|
@ -70,22 +72,65 @@ const CriteriaTable = ({ data }: { data: TableRow[] }) => (
|
||||||
const isCompactMenu = false
|
const isCompactMenu = false
|
||||||
const Profile = () => {
|
const Profile = () => {
|
||||||
const user = useAppSelector((state) => state.authentication.user)
|
const user = useAppSelector((state) => state.authentication.user)
|
||||||
|
const userData = getUser()
|
||||||
|
|
||||||
const [opened, setOpened] = useState(false)
|
const [opened, setOpened] = useState(false)
|
||||||
const [dataChange, setDataChange] = useState({
|
const [dataChange, setDataChange] = useState({
|
||||||
password: '',
|
password: '',
|
||||||
new_password: '',
|
new_password: '',
|
||||||
confirm_password: '',
|
confirm_password: '',
|
||||||
})
|
})
|
||||||
|
const [avatar, setAvatar] = useState(user.user.avatar)
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const [dataProfile, setDataProfile] = useState<any>([])
|
const [dataProfile, setDataProfile] = useState<any>([])
|
||||||
|
|
||||||
// const [projects, setProjects] = useState(projectsData)
|
|
||||||
const [expandedProjects, setExpandedProjects] = useState<ExpandedProjects>({})
|
const [expandedProjects, setExpandedProjects] = useState<ExpandedProjects>({})
|
||||||
const [expandedSprints, setExpandedSprints] = useState<ExpandedSprints>({})
|
const [expandedSprints, setExpandedSprints] = useState<ExpandedSprints>({})
|
||||||
|
|
||||||
const [countSpam, setCountSpam] = useState(0)
|
const [countSpam, setCountSpam] = useState(0)
|
||||||
|
|
||||||
|
const [selectedAvatar, setSelectedAvatar] = useState<string | null>(null)
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
|
|
||||||
|
const updateAvatar = async (file: File) => {
|
||||||
|
try {
|
||||||
|
const res = await postImage(updateProfilesData, file, 'post')
|
||||||
|
if (res.status) {
|
||||||
|
return res.data
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
notifications.show({
|
||||||
|
title: 'Error',
|
||||||
|
message: error.message ?? error,
|
||||||
|
color: 'red',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleAvatarChange = (event: any) => {
|
||||||
|
const file = event.target.files[0]
|
||||||
|
if (file) {
|
||||||
|
setSelectedAvatar(URL.createObjectURL(file)) // Hiển thị ảnh mới đã chọn tạm thời
|
||||||
|
const fetchData = async () => {
|
||||||
|
const result = await updateAvatar(file)
|
||||||
|
notifications.show({
|
||||||
|
title: 'Success',
|
||||||
|
message: 'Update avatar success',
|
||||||
|
color: 'green',
|
||||||
|
})
|
||||||
|
if (userData) {
|
||||||
|
let userObj = JSON.parse(userData)
|
||||||
|
userObj.user.avatar = result
|
||||||
|
let updatedUserData = JSON.stringify(userObj)
|
||||||
|
localStorage.setItem('user', updatedUserData)
|
||||||
|
dispatch(success({ user: userObj }))
|
||||||
|
}
|
||||||
|
//Update avatar
|
||||||
|
setAvatar(result ?? [])
|
||||||
|
}
|
||||||
|
fetchData()
|
||||||
|
}
|
||||||
|
}
|
||||||
const passwordRegex =
|
const passwordRegex =
|
||||||
/^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/
|
/^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/
|
||||||
|
|
||||||
|
|
@ -222,16 +267,31 @@ const Profile = () => {
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
accept="image/*"
|
||||||
|
style={{ display: 'none' }}
|
||||||
|
id="avatar-input"
|
||||||
|
onChange={handleAvatarChange}
|
||||||
|
/>
|
||||||
<Avatar
|
<Avatar
|
||||||
src={null || ''}
|
src={
|
||||||
|
selectedAvatar
|
||||||
|
? selectedAvatar
|
||||||
|
: avatar !== ''
|
||||||
|
? import.meta.env.VITE_BACKEND_URL + 'storage/' + avatar
|
||||||
|
: ''
|
||||||
|
}
|
||||||
alt="User Avatar"
|
alt="User Avatar"
|
||||||
size={150}
|
size={150}
|
||||||
// radius="xl"
|
// radius="xl"
|
||||||
mb={5}
|
mb={5}
|
||||||
mt={5}
|
mt={5}
|
||||||
onClick={() => {
|
onClick={() =>
|
||||||
console.log('update avatar')
|
(
|
||||||
}}
|
document.getElementById('avatar-input') as HTMLElement
|
||||||
|
).click()
|
||||||
|
}
|
||||||
style={{
|
style={{
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
// border: '2px solid black',
|
// border: '2px solid black',
|
||||||
|
|
|
||||||
|
|
@ -105,13 +105,18 @@ export const deleteApi = async (url: any) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const postImage = async (url: string, body: any) => {
|
export const postImage = async (url: string, body: any, method: any) => {
|
||||||
const header = await getFormDataHeader()
|
const header = await getFormDataHeader()
|
||||||
const formData = new FormData()
|
const formData = new FormData()
|
||||||
formData.append('file', body)
|
formData.append('file', body)
|
||||||
try {
|
try {
|
||||||
const resp = await axios.put(url, formData, header)
|
if (method === 'post') {
|
||||||
return handleResponse(resp)
|
const resp = await axios.post(url, formData, header)
|
||||||
|
return handleResponse(resp)
|
||||||
|
} else {
|
||||||
|
const resp = await axios.put(url, formData, header)
|
||||||
|
return handleResponse(resp)
|
||||||
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
throw handleResponse(err.response)
|
throw handleResponse(err.response)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue