toi uu authen, add loading global
This commit is contained in:
13
src/App.tsx
13
src/App.tsx
@@ -1,9 +1,18 @@
|
||||
import { createContext } from 'react'
|
||||
import RouterSetup from './RouterSetup'
|
||||
import { useSelector } from 'react-redux'
|
||||
import type { StoreType } from './stores'
|
||||
import Loading from './components/Loading'
|
||||
|
||||
export default function App() {
|
||||
|
||||
const userStore = useSelector((store: StoreType) => store.user)
|
||||
|
||||
return (
|
||||
<RouterSetup />
|
||||
<>
|
||||
{
|
||||
userStore.loading ? <Loading />
|
||||
: <RouterSetup />
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import axios from "axios"
|
||||
import type { User } from "../../types/user.type"
|
||||
import { message } from "antd"
|
||||
import { ApiUtil } from "../../utils/api.util"
|
||||
import * as jose from 'jose'
|
||||
|
||||
export interface UserSignInDTO {
|
||||
emailOrUserName: string
|
||||
@@ -16,10 +17,7 @@ export interface UserFindAllDTO {
|
||||
}
|
||||
|
||||
export const UserApi = {
|
||||
signIn: async (data: UserSignInDTO): Promise<{
|
||||
message: string
|
||||
data: any
|
||||
}> => {
|
||||
signIn: async (data: UserSignInDTO) => {
|
||||
let userData = await axios.get(`${import.meta.env.VITE_SV_HOST}/users?email=${data.emailOrUserName}`)
|
||||
if (userData.data.length == 0) {
|
||||
userData = await axios.get(`${import.meta.env.VITE_SV_HOST}/users?userName=${data.emailOrUserName}`)
|
||||
@@ -36,10 +34,7 @@ export const UserApi = {
|
||||
data: null
|
||||
})
|
||||
}
|
||||
return {
|
||||
message: "Đăng nhập thành công!",
|
||||
data: userData.data[0] as User
|
||||
}
|
||||
return createToken(userData.data[0].id)
|
||||
}
|
||||
},
|
||||
signUp: async (data: User) => {
|
||||
@@ -106,5 +101,59 @@ export const UserApi = {
|
||||
findAll: async (query?: UserFindAllDTO) => {
|
||||
let result = await axios.get(`${import.meta.env.VITE_SV_HOST}/users?` + ApiUtil.writeQuery(query))
|
||||
return result.data
|
||||
},
|
||||
me: async (token: string) => {
|
||||
let tokenData = await decodeToken(token)
|
||||
|
||||
if (!tokenData) {
|
||||
throw ({
|
||||
message: "Token không chính xác!"
|
||||
})
|
||||
}
|
||||
|
||||
let { userId } = tokenData;
|
||||
|
||||
let getUserByIdRes = await axios.get(`${import.meta.env.VITE_SV_HOST}/users/${userId}`)
|
||||
|
||||
if (!getUserByIdRes.data) {
|
||||
throw ({
|
||||
message: "Lỗi lấy dữ liệu"
|
||||
})
|
||||
}
|
||||
|
||||
let data = await new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve(getUserByIdRes.data)
|
||||
}, 1000)
|
||||
})
|
||||
|
||||
return data
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function createToken(userId: string) {
|
||||
const secret = new TextEncoder().encode(import.meta.env.VITE_JWT_TOKEN);
|
||||
|
||||
const token = await new jose.SignJWT({ userId })
|
||||
.setProtectedHeader({ alg: 'HS256' })
|
||||
.setIssuedAt()
|
||||
.setExpirationTime('2h')
|
||||
.sign(secret);
|
||||
return token
|
||||
}
|
||||
|
||||
async function decodeToken(token: string) {
|
||||
try {
|
||||
const secret = new TextEncoder().encode(import.meta.env.VITE_JWT_TOKEN);
|
||||
|
||||
const { payload } = await jose.jwtVerify(token, secret, {
|
||||
algorithms: ['HS256'],
|
||||
});
|
||||
|
||||
return payload;
|
||||
} catch (error) {
|
||||
console.error('Token không hợp lệ hoặc đã hết hạn:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
22
src/components/Loading.tsx
Normal file
22
src/components/Loading.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import React from 'react'
|
||||
|
||||
export default function Loading() {
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex flex-col items-center justify-center bg-white/70 backdrop-blur-sm">
|
||||
<div className="relative mb-4">
|
||||
<div className="h-14 w-14 rounded-full bg-gray-200 animate-pulse" />
|
||||
<svg
|
||||
className="absolute -right-2 -bottom-2 h-7 w-7 animate-spin"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden
|
||||
>
|
||||
<circle cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="3" className="text-gray-300 opacity-70" />
|
||||
<path d="M22 12a10 10 0 00-10-10" stroke="currentColor" strokeWidth="3" className="text-indigo-500" strokeLinecap="round" />
|
||||
</svg>
|
||||
</div>
|
||||
<div className="text-gray-600 font-medium">Đang tải dữ liệu xác thực...</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -14,7 +14,7 @@ export default function ProtectedAdmin(
|
||||
return store.user
|
||||
})
|
||||
|
||||
|
||||
/* Đã đăng nhập và là master hoặc admin */
|
||||
if (userStore.data?.role == UserRole.MASTER || userStore.data?.role == UserRole.ADMIN) {
|
||||
return (
|
||||
<>
|
||||
@@ -23,7 +23,8 @@ export default function ProtectedAdmin(
|
||||
)
|
||||
}
|
||||
|
||||
if (!userStore.data?.role) {
|
||||
/* chưa đăng nhập */
|
||||
if (!userStore.data && !userStore.loading) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900 p-4">
|
||||
<div className="w-full max-w-md bg-white rounded-2xl shadow-2xl overflow-hidden">
|
||||
@@ -90,11 +91,16 @@ export default function ProtectedAdmin(
|
||||
)
|
||||
}
|
||||
|
||||
/* đã đăng nhập nhưng không phải master hoặc admin */
|
||||
if (userStore.data?.role) {
|
||||
window.location.href="/"
|
||||
window.location.href = "/"
|
||||
return <></>
|
||||
}
|
||||
|
||||
|
||||
/* chưa vào case, default */
|
||||
|
||||
|
||||
async function signInHandle(e: FormEvent) {
|
||||
e.preventDefault()
|
||||
let data: UserSignInDTO = {
|
||||
@@ -103,10 +109,9 @@ export default function ProtectedAdmin(
|
||||
}
|
||||
try {
|
||||
let result = await Apis.user.signIn(data)
|
||||
localStorage.setItem("userLogin", JSON.stringify(result.data))
|
||||
localStorage.setItem("token", result)
|
||||
Modal.confirm({
|
||||
title: "Đăng nhập thành công",
|
||||
content: result.message,
|
||||
onOk: () => {
|
||||
window.location.reload()
|
||||
},
|
||||
|
||||
@@ -28,7 +28,7 @@ export default function HeaderCom({ collapsed, setCollapsed }: { collapsed: bool
|
||||
</select>
|
||||
|
||||
<Button onClick={() => {
|
||||
localStorage.removeItem("userLogin")
|
||||
localStorage.removeItem("token")
|
||||
window.location.reload()
|
||||
}}>logout</Button>
|
||||
</Header>
|
||||
|
||||
@@ -49,9 +49,9 @@ export default function Auth() {
|
||||
console.log("loginData", loginData)
|
||||
try {
|
||||
let data = await Apis.user.signIn(loginData)
|
||||
localStorage.setItem("userLogin", data.data.id)
|
||||
localStorage.setItem("token", data)
|
||||
Modal.confirm({
|
||||
title: `Chào mừng ${data.data.userName} đã quay trở lại`,
|
||||
title: `Chào mừng bạn đã quay trở lại`,
|
||||
content: ``,
|
||||
onOk: () => {
|
||||
window.location.href = "/"
|
||||
|
||||
@@ -70,7 +70,7 @@ export default function Header() {
|
||||
window.location.href = "/admin"
|
||||
}} className="fa-solid fa-lock"></i>}
|
||||
<i onClick={() => {
|
||||
window.localStorage.removeItem("userLogin")
|
||||
window.localStorage.removeItem("token")
|
||||
window.location.href = "/auth"
|
||||
}} className="cursor-pointer fa-solid fa-right-from-bracket"></i>
|
||||
</div>
|
||||
|
||||
@@ -21,8 +21,16 @@ const userSlice = createSlice({
|
||||
|
||||
},
|
||||
extraReducers: (bd) => {
|
||||
bd.addCase(fetchUserData.pending, (state, action) => {
|
||||
state.loading = true
|
||||
})
|
||||
bd.addCase(fetchUserData.fulfilled, (state, action) => {
|
||||
state.data = action.payload
|
||||
console.log("đã vào full", action.payload)
|
||||
state.loading = false
|
||||
state.data = action.payload
|
||||
})
|
||||
bd.addCase(fetchUserData.rejected, (state, action) => {
|
||||
state.loading = false
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -31,8 +39,8 @@ const userSlice = createSlice({
|
||||
const fetchUserData = createAsyncThunk(
|
||||
"user/fetchUserData",
|
||||
async () => {
|
||||
let result = await Apis.user.findById(localStorage.getItem("userLogin"))
|
||||
return result.data
|
||||
let result = await Apis.user.me(localStorage.getItem("token")) as any
|
||||
return result
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user