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. แนวทางการกรอกข้อมูล
+
+ - ชื่อผู้ใช้งาน: ใช้ตัวอักษร/ตัวเลขเท่านั้น (ขั้นต่ำ 4 ตัว)
+ - รหัสผ่าน: ต้องยาวอย่างน้อย 8 ตัวอักษร
+ - เบอร์โทรศัพท์: เป็นตัวเลข 10 หลัก (ไม่บังคับ)
+
+
+
+
+ {/* 2. ช่องทางติดต่อช่วยเหลือ */}
+
2. ช่องทางติดต่อช่วยเหลือ
+
+ -
+
+ Email: support@ddo.tech
+
+ -
+
+ โทร: 02-XXX-XXXX
+
+
+
+ );
+}
\ 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
+
+
+
+
+
+
+
+ );
+}
\ 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