diff --git a/web/src/components/Registration/RegisterInfoCard.jsx b/web/src/components/Registration/RegisterInfoCard.jsx new file mode 100644 index 0000000..acef857 --- /dev/null +++ b/web/src/components/Registration/RegisterInfoCard.jsx @@ -0,0 +1,37 @@ +import React from 'react'; +import { FaEnvelope, FaQuestionCircle, FaPhoneAlt } from 'react-icons/fa'; + +export default function RegisterInfoCard() { + return ( + // ใช้ w-full h-full เพื่อให้ Component ขยายเต็มพื้นที่ Container +
+
+ + คำแนะนำและช่วยเหลือ +
+ + {/* 1. แนวทางการกรอกข้อมูล */} +

1. แนวทางการกรอกข้อมูล

+ + +
+ + {/* 2. ช่องทางติดต่อช่วยเหลือ */} +

2. ช่องทางติดต่อช่วยเหลือ

+ +
+ ); +} \ No newline at end of file diff --git a/web/src/pages/auth/RegisterPage.jsx b/web/src/pages/auth/RegisterPage.jsx new file mode 100644 index 0000000..f9a7ed8 --- /dev/null +++ b/web/src/pages/auth/RegisterPage.jsx @@ -0,0 +1,83 @@ +import { Link } from 'react-router-dom'; +import { useForm } from 'react-hook-form'; +import { yupResolver } from '@hookform/resolvers/yup'; + +import { useRegisterMutation } from '../../services/authApi'; +import { registrationSchema } from '../../schemas/authSchema'; +import InputText from '../../components/InputText'; +import RegisterInfoCard from '../../components/Registration/RegisterInfoCard'; + + +export default function RegisterPage() { + const registerMutation = useRegisterMutation(); + + // ... (Hook Form Setup และ Logic เหมือนเดิม) ... + const { register, handleSubmit, formState: { errors } } = useForm({ + resolver: yupResolver(registrationSchema), + defaultValues: { username: '', email: '', phone_number: '', password: '', confirm_password: '' } + }); + + const onSubmit = (data) => { + const { confirm_password: _, ...payload } = data; + registerMutation.mutate(payload); + }; + + const loading = registerMutation.isPending; + + return ( + // Card หลัก: ใช้ h-full เพื่อให้เต็มหน้าจอ (ตาม AuthLayout.jsx ที่ถูกปรับ) +
+ + {/* Grid Layout: แบ่งเป็น 12 ส่วน, 6 ส่วนสำหรับ Guide (ซ้าย), 6 ส่วนสำหรับ Form (ขวา) */} +
+ + {/* 1. คอลัมน์ซ้าย (Guide/Info Card) */} +
+ +
+ + {/* 2. คอลัมน์ขวา (Form หลัก) */} + {/* ใช้พื้นที่ 6/12 คอลัมน์ (50%), แสดง Form ที่นี่ */} +
+
+ +

สร้างบัญชี DDO Console

+

กรุณากรอกข้อมูลที่จำเป็นทั้งหมดเพื่อเข้าใช้งานระบบ MLOps Gateway

+ +
+ + {/* ... (ErrorText และ Input Fields ทั้งหมดเหมือนเดิม) ... */} + +
+ + + + + +
+ + + +
+ เป็นสมาชิกอยู่แล้ว? + + + เข้าสู่ระบบ + + +
+
+
+
+ +
+
+ ); +} \ No newline at end of file diff --git a/web/src/routes/AuthRoutes.jsx b/web/src/routes/AuthRoutes.jsx index 0714003..2855b93 100644 --- a/web/src/routes/AuthRoutes.jsx +++ b/web/src/routes/AuthRoutes.jsx @@ -1,6 +1,7 @@ import { Route, Routes, Navigate } from "react-router-dom"; import AuthLayout from "../layouts/AuthLayout.jsx"; import LoginForm from "../components/LoginForm.jsx"; +import RegisterPage from "../pages/auth/RegisterPage.jsx"; export default function AuthRoutes() { @@ -12,7 +13,7 @@ export default function AuthRoutes() { {/* 2. AuthLayout สำหรับหน้า Login, Register, Forgot Password */} }> }/> - หน้าลงทะเบียน}/> + }/> หน้าลืมรหัสผ่าน}/> diff --git a/web/src/schemas/authSchema.js b/web/src/schemas/authSchema.js index d663bc8..28f8394 100644 --- a/web/src/schemas/authSchema.js +++ b/web/src/schemas/authSchema.js @@ -6,4 +6,15 @@ import * as yup from 'yup'; export const loginSchema = yup.object().shape({ username: yup.string().required('กรุณากรอกชื่อผู้ใช้งาน'), password: yup.string().required('กรุณากรอกรหัสผ่าน'), +}); + +// Schema สำหรับตรวจสอบข้อมูล Registration +export const registrationSchema = yup.object().shape({ + username: yup.string().required('กรุณากรอกชื่อผู้ใช้งาน').min(4, 'ชื่อผู้ใช้ต้องมีความยาวอย่างน้อย 4 ตัวอักษร'), + email: yup.string().email('รูปแบบอีเมลไม่ถูกต้อง').required('กรุณากรอกอีเมล'), + phone_number: yup.string().nullable().matches(/^[0-9]*$/, 'เบอร์โทรศัพท์ต้องเป็นตัวเลขเท่านั้น'), + password: yup.string().required('กรุณากรอกรหัสผ่าน').min(8, 'รหัสผ่านต้องมีความยาวอย่างน้อย 8 ตัวอักษร'), + confirm_password: yup.string() + .oneOf([yup.ref('password'), null], 'รหัสผ่านไม่ตรงกัน') + .required('กรุณายืนยันรหัสผ่าน'), }); \ No newline at end of file diff --git a/web/src/services/authApi.js b/web/src/services/authApi.js index ef97530..7a2fd74 100644 --- a/web/src/services/authApi.js +++ b/web/src/services/authApi.js @@ -3,6 +3,7 @@ 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; @@ -136,4 +137,56 @@ export const useLoginMutation = () => { 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); + }, + }); }; \ No newline at end of file