192 lines
8.2 KiB
JavaScript
192 lines
8.2 KiB
JavaScript
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|
import { useDispatch } from 'react-redux';
|
|
import { loginSuccess } from '../features/auth/authSlice';
|
|
import { useNavigate } from 'react-router-dom';
|
|
import axios from 'axios'; // ใช้ Axios ธรรมดาสำหรับการเรียกที่ยังไม่มี Token
|
|
import { addToast } from '../features/toast/toastSlice';
|
|
|
|
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL;
|
|
|
|
// ----------------------------------------------------
|
|
// Helper Functions
|
|
// ----------------------------------------------------
|
|
|
|
/**
|
|
* แปลง Object ให้เป็นฟอร์มข้อมูล (x-www-form-urlencoded)
|
|
*/
|
|
const toFormUrlEncoded = (data) => {
|
|
return Object.keys(data)
|
|
.map(key => encodeURIComponent(key) + '=' + encodeURIComponent(data[key]))
|
|
.join('&');
|
|
};
|
|
|
|
/**
|
|
* กำหนดบทบาท (Role) จาก Django User Object ที่สมบูรณ์
|
|
* @param {object} user - User Object จาก /users/me/
|
|
*/
|
|
const determineRole = (user) => {
|
|
if (!user || !user.id) {
|
|
return 'guest';
|
|
}
|
|
|
|
// 1. ใช้ฟิลด์ 'role' ที่ส่งมาจาก Backend โดยตรง (ถ้ามี)
|
|
if (user.role) {
|
|
return user.role.toLowerCase(); // เช่น 'ADMIN' -> 'admin'
|
|
}
|
|
|
|
// 2. ใช้ฟิลด์ is_superuser/is_staff เป็น Fallback/เกณฑ์มาตรฐาน
|
|
if (user.is_superuser) {
|
|
return 'admin';
|
|
}
|
|
if (user.is_staff) {
|
|
// ถ้าเป็น is_staff แต่ไม่ใช่ superuser
|
|
return 'manager';
|
|
}
|
|
|
|
// ผู้ใช้ทั่วไป
|
|
return 'viewer';
|
|
};
|
|
|
|
|
|
/**
|
|
* ฟังก์ชันหลักในการล็อกอิน: ขั้นตอนที่ 1 (รับ Token) และ ขั้นตอนที่ 2 (รับ User Object)
|
|
*/
|
|
const loginUser = async (credentials) => {
|
|
// credentials จะเป็น { username, password }
|
|
const formData = toFormUrlEncoded(credentials);
|
|
|
|
let access, refresh;
|
|
|
|
// ---------------------------------------------
|
|
// ขั้นตอนที่ 1: รับ Access/Refresh Token (POST /jwt/create/)
|
|
// ---------------------------------------------
|
|
try {
|
|
const tokenResponse = await axios.post(`${API_BASE_URL}/api/v1/auth/jwt/create/`, formData, {
|
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
|
|
});
|
|
|
|
access = tokenResponse.data.access;
|
|
refresh = tokenResponse.data.refresh;
|
|
|
|
} catch (error) {
|
|
// จัดการ Error จากการล็อกอิน
|
|
const errorMessage = error.response?.data?.detail || "ชื่อผู้ใช้หรือรหัสผ่านไม่ถูกต้อง";
|
|
throw new Error(errorMessage);
|
|
}
|
|
|
|
// ---------------------------------------------
|
|
// ขั้นตอนที่ 2: ใช้ Access Token เพื่อดึง User Object (GET /users/me/)
|
|
// ---------------------------------------------
|
|
let user;
|
|
try {
|
|
const userResponse = await axios.get(`${API_BASE_URL}/api/v1/auth/users/me/`, {
|
|
headers: {
|
|
// ใช้ JWT เพื่อยืนยันตัวตน
|
|
'Authorization': `Bearer ${access}`,
|
|
},
|
|
});
|
|
|
|
user = userResponse.data;
|
|
|
|
} catch (error) {
|
|
console.error("Failed to fetch user data after token creation:", error);
|
|
throw new Error("ล็อกอินสำเร็จ แต่ไม่สามารถดึงข้อมูลผู้ใช้ได้ (กรุณาติดต่อผู้ดูแลระบบ)");
|
|
}
|
|
|
|
// ---------------------------------------------
|
|
// ขั้นตอนที่ 3: คำนวณ Role และส่งกลับข้อมูล
|
|
// ---------------------------------------------
|
|
const userRole = determineRole(user);
|
|
|
|
return {
|
|
access_token: access,
|
|
refresh_token: refresh,
|
|
user: user,
|
|
role: userRole,
|
|
};
|
|
};
|
|
|
|
|
|
// ----------------------------------------------------
|
|
// Custom Hook สำหรับจัดการการล็อกอิน
|
|
// ----------------------------------------------------
|
|
export const useLoginMutation = () => {
|
|
const dispatch = useDispatch();
|
|
const navigate = useNavigate();
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: loginUser,
|
|
onSuccess: (data) => {
|
|
|
|
// 1. จัดการ Token และ User Data
|
|
localStorage.setItem('token', data.access_token);
|
|
localStorage.setItem('user', JSON.stringify(data.user));
|
|
localStorage.setItem('role', data.role); // บันทึก Role ที่ถูกต้อง
|
|
|
|
// 2. อัปเดต Redux State
|
|
dispatch(loginSuccess({ user: data.user, role: data.role }));
|
|
|
|
// 3. นำทางผู้ใช้
|
|
navigate('/dashboard', { replace: true });
|
|
queryClient.invalidateQueries({ queryKey: ['userData'] });
|
|
},
|
|
onError: (error) => {
|
|
// error.message ถูกโยนมาจาก loginUser function
|
|
console.error('Login API Error:', error.message);
|
|
throw new Error(error.message);
|
|
},
|
|
});
|
|
};
|
|
|
|
// ----------------------------------------------------
|
|
// Helper Function: Registration API Call
|
|
// ----------------------------------------------------
|
|
const registerNewUser = async (userData) => {
|
|
// Djoser Endpoint: POST /api/v1/auth/users/ (รับ username, email, password, phone_number)
|
|
// Djoser /users/ Endpoint รับ JSON Body ได้โดยตรง (ไม่ใช้ x-www-form-urlencoded)
|
|
|
|
// ลบ confirm_password ออกจาก payload ก่อนส่ง
|
|
// ใช้ _ นำหน้าเพื่อบอก ESLint ว่าตัวแปรนี้จะไม่ถูกใช้
|
|
// ต้องการ confirm_password เพื่อให้ Yup (Validation) ทำงาน แต่เราไม่ต้องการส่งมันไปยัง API Backend (เพราะ Djoser API ไม่ได้รับ Field นี้)
|
|
const { confirm_password: _, ...payload } = userData;
|
|
|
|
try {
|
|
const response = await axios.post(`${API_BASE_URL}/api/v1/auth/users/`, payload);
|
|
return response.data;
|
|
|
|
} catch (error) {
|
|
// จัดการ Error เช่น Username/Email ซ้ำ
|
|
const errorDetail = error.response?.data;
|
|
let errorMessage = "การลงทะเบียนล้มเหลว โปรดตรวจสอบข้อมูลอีกครั้ง";
|
|
|
|
if (errorDetail) {
|
|
if (errorDetail.username) errorMessage = `ชื่อผู้ใช้งาน: ${errorDetail.username[0]}`;
|
|
else if (errorDetail.email) errorMessage = `อีเมล: ${errorDetail.email[0]}`;
|
|
else if (errorDetail.phone_number) errorMessage = `เบอร์โทรศัพท์: ${errorDetail.phone_number[0]}`;
|
|
}
|
|
|
|
throw new Error(errorMessage);
|
|
}
|
|
};
|
|
|
|
// ----------------------------------------------------
|
|
// Custom Hook สำหรับจัดการการลงทะเบียน
|
|
// ----------------------------------------------------
|
|
export const useRegisterMutation = () => {
|
|
const dispatch = useDispatch();
|
|
const navigate = useNavigate();
|
|
|
|
return useMutation({
|
|
mutationFn: registerNewUser,
|
|
onSuccess: () => {
|
|
dispatch(addToast({ message: 'ลงทะเบียนสำเร็จ! คุณสามารถเข้าสู่ระบบได้ทันที', type: 'success' }));
|
|
navigate('/login');
|
|
},
|
|
onError: (error) => {
|
|
// ส่ง Toast แจ้งเตือนข้อผิดพลาดที่โยนมาจาก registerNewUser
|
|
dispatch(addToast({ message: `ลงทะเบียนล้มเหลว: ${error.message}`, type: 'error' }));
|
|
throw new Error(error.message);
|
|
},
|
|
});
|
|
}; |