diff --git a/fe-admin/src/components/ChangePasswordDialog.tsx b/fe-admin/src/components/ChangePasswordDialog.tsx new file mode 100644 index 0000000..35ea66b --- /dev/null +++ b/fe-admin/src/components/ChangePasswordDialog.tsx @@ -0,0 +1,115 @@ +import { useState } from 'react' +import { useMutation } from '@tanstack/react-query' +import { toast } from 'sonner' +import { KeyRound } from 'lucide-react' +import { Dialog } from '@/components/ui/Dialog' +import { Button } from '@/components/ui/Button' +import { Input } from '@/components/ui/Input' +import { Label } from '@/components/ui/Label' +import { api } from '@/lib/api' +import { getErrorMessage } from '@/lib/apiError' + +type Props = { open: boolean; onClose: () => void } + +// Self-service đổi mật khẩu — user tự đổi (nhập mật khẩu hiện tại để xác thực). +// BE: POST /auth/change-password (Identity verify pass cũ). Khác admin Reset. +export function ChangePasswordDialog({ open, onClose }: Props) { + const [current, setCurrent] = useState('') + const [next, setNext] = useState('') + const [confirm, setConfirm] = useState('') + + const close = () => { + setCurrent('') + setNext('') + setConfirm('') + onClose() + } + + const mutation = useMutation({ + mutationFn: () => + api.post('/auth/change-password', { currentPassword: current, newPassword: next }), + onSuccess: () => { + toast.success('Đổi mật khẩu thành công. Lần đăng nhập sau dùng mật khẩu mới.') + close() + }, + onError: err => toast.error(getErrorMessage(err)), + }) + + const newTooShort = next.length > 0 && next.length < 12 + const sameAsOld = next.length > 0 && next === current + const mismatch = confirm.length > 0 && next !== confirm + const canSubmit = + current.length > 0 && next.length >= 12 && next === confirm && next !== current && !mutation.isPending + + const submit = () => { + if (canSubmit) mutation.mutate() + } + + return ( + + Đổi mật khẩu + + } + footer={ + <> + + + + } + > +
{ + e.preventDefault() + submit() + }} + > +
+ + setCurrent(e.target.value)} + /> +
+
+ + setNext(e.target.value)} + /> + {newTooShort &&

Mật khẩu mới phải có ít nhất 12 ký tự.

} + {sameAsOld &&

Mật khẩu mới phải khác mật khẩu hiện tại.

} +
+
+ + setConfirm(e.target.value)} + /> + {mismatch &&

Mật khẩu nhập lại không khớp.

} +
+

Mật khẩu cần tối thiểu 12 ký tự.

+ {/* cho phép submit bằng Enter */} +
+ ) +} diff --git a/fe-admin/src/components/TopBar.tsx b/fe-admin/src/components/TopBar.tsx index 43a0777..c99b9aa 100644 --- a/fe-admin/src/components/TopBar.tsx +++ b/fe-admin/src/components/TopBar.tsx @@ -1,12 +1,14 @@ import { useEffect, useRef, useState } from 'react' -import { ChevronDown, LogOut } from 'lucide-react' +import { ChevronDown, LogOut, KeyRound } from 'lucide-react' import { useAuth } from '@/contexts/AuthContext' +import { ChangePasswordDialog } from '@/components/ChangePasswordDialog' import { NotificationBell } from '@/components/NotificationBell' import { cn } from '@/lib/cn' function UserMenu() { const { user, logout } = useAuth() const [open, setOpen] = useState(false) + const [pwOpen, setPwOpen] = useState(false) const ref = useRef(null) useEffect(() => { @@ -53,6 +55,16 @@ function UserMenu() { )} + + + + } + > +
{ + e.preventDefault() + submit() + }} + > +
+ + setCurrent(e.target.value)} + /> +
+
+ + setNext(e.target.value)} + /> + {newTooShort &&

Mật khẩu mới phải có ít nhất 12 ký tự.

} + {sameAsOld &&

Mật khẩu mới phải khác mật khẩu hiện tại.

} +
+
+ + setConfirm(e.target.value)} + /> + {mismatch &&

Mật khẩu nhập lại không khớp.

} +
+

Mật khẩu cần tối thiểu 12 ký tự.

+ {/* cho phép submit bằng Enter */} +