พัฒนา Frontend หน้าลงทะเบียน (Registration Page)

This commit is contained in:
Flook 2025-11-15 08:39:57 +07:00
parent 3a4d6c245c
commit 248a31b13f
5 changed files with 186 additions and 1 deletions

View File

@ -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
<div className="w-full h-full p-0 flex flex-col">
<div className="flex items-center text-xl font-bold text-primary mb-4 border-b border-base-200 pb-3">
<FaQuestionCircle className="w-6 h-6 mr-3 text-warning" />
คำแนะนำและชวยเหล
</div>
{/* 1. แนวทางการกรอกข้อมูล */}
<h3 className="text-lg font-semibold text-base-content mb-2">1. แนวทางการกรอกขอม</h3>
<ul className="list-disc list-inside space-y-2 text-sm text-base-content/70 ml-2">
<li><span className="font-semibold">อผใชงาน:</span> ใชวอกษร/วเลขเทาน (นต 4 )</li>
<li><span className="font-semibold">รหสผาน:</span> องยาวอยางนอย 8 วอกษร</li>
<li><span className="font-semibold">เบอรโทรศพท:</span> เปนตวเลข 10 หล (ไมงค)</li>
</ul>
<div className="divider my-4"></div>
{/* 2. ช่องทางติดต่อช่วยเหลือ */}
<h3 className="text-lg font-semibold text-base-content mb-2">2. องทางตดตอชวยเหล</h3>
<ul className="space-y-2 text-sm text-base-content/70 ml-2">
<li className='flex items-center'>
<FaEnvelope className="w-4 h-4 mr-2 text-info" />
<span className="font-semibold">Email:</span> support@ddo.tech
</li>
<li className='flex items-center'>
<FaPhoneAlt className="w-4 h-4 mr-2 text-info" />
<span className="font-semibold">โทร:</span> 02-XXX-XXXX
</li>
</ul>
</div>
);
}

View File

@ -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 )
<div className="card w-full h-full bg-base-100 rounded-none shadow-none">
{/* Grid Layout: แบ่งเป็น 12 ส่วน, 6 ส่วนสำหรับ Guide (ซ้าย), 6 ส่วนสำหรับ Form (ขวา) */}
<div className="grid grid-cols-1 md:grid-cols-12 h-full">
{/* 1. คอลัมน์ซ้าย (Guide/Info Card) */}
<div className="col-span-1 md:col-span-6 bg-base-200 p-8 h-full flex flex-col justify-center">
<RegisterInfoCard />
</div>
{/* 2. คอลัมน์ขวา (Form หลัก) */}
{/* ใช้พื้นที่ 6/12 คอลัมน์ (50%), แสดง Form ที่นี่ */}
<div className="col-span-1 md:col-span-6 py-12 px-8 lg:px-16 flex flex-col justify-center items-center
border-l border-base-300 md:border-l-0">
<div className="w-full max-w-lg">
<h2 className='text-3xl font-bold mb-8 text-center text-gray-800'>สรางบญช DDO Console</h2>
<p className="text-sm text-center text-gray-600 mb-6">กรณากรอกขอมลทจำเปนทงหมดเพอเขาใชงานระบบ MLOps Gateway</p>
<form onSubmit={handleSubmit(onSubmit)}>
{/* ... (ErrorText และ Input Fields ทั้งหมดเหมือนเดิม) ... */}
<div className="space-y-4">
<InputText labelTitle="ชื่อผู้ใช้งาน" placeholder="เช่น John.Doe2568" {...register('username')} error={errors.username} />
<InputText type="email" labelTitle="อีเมล" placeholder="จำเป็นสำหรับการกู้คืนรหัสผ่าน" {...register('email')} error={errors.email} />
<InputText type="tel" labelTitle="เบอร์โทรศัพท์ (ไม่บังคับ)" placeholder="สำหรับติดต่อเร่งด่วน" {...register('phone_number')} error={errors.phone_number} />
<InputText type="password" labelTitle="รหัสผ่าน" placeholder="ความยาวอย่างน้อย 8 ตัวอักษร" {...register('password')} error={errors.password} />
<InputText type="password" labelTitle="ยืนยันรหัสผ่าน" placeholder="กรุณากรอกรหัสผ่านซ้ำ" {...register('confirm_password')} error={errors.confirm_password} />
</div>
<button
type="submit"
className={"btn mt-8 w-full btn-primary rounded-md shadow-lg" + (loading ? " loading" : "")}
disabled={loading}
>
{loading ? 'กำลังลงทะเบียน...' : 'ลงทะเบียน'}
</button>
<div className='text-center mt-6 text-gray-600'>
เปนสมาชกอยแล?
<Link to="/login">
<span className="inline-block text-blue-600 hover:underline ml-1">
เขาสระบบ
</span>
</Link>
</div>
</form>
</div>
</div>
</div>
</div>
);
}

View File

@ -1,6 +1,7 @@
import { Route, Routes, Navigate } from "react-router-dom"; import { Route, Routes, Navigate } from "react-router-dom";
import AuthLayout from "../layouts/AuthLayout.jsx"; import AuthLayout from "../layouts/AuthLayout.jsx";
import LoginForm from "../components/LoginForm.jsx"; import LoginForm from "../components/LoginForm.jsx";
import RegisterPage from "../pages/auth/RegisterPage.jsx";
export default function AuthRoutes() { export default function AuthRoutes() {
@ -12,7 +13,7 @@ export default function AuthRoutes() {
{/* 2. AuthLayout สำหรับหน้า Login, Register, Forgot Password */} {/* 2. AuthLayout สำหรับหน้า Login, Register, Forgot Password */}
<Route element={<AuthLayout/>}> <Route element={<AuthLayout/>}>
<Route path="/login" element={<LoginForm/>}/> <Route path="/login" element={<LoginForm/>}/>
<Route path="/register" element={<div>หนาลงทะเบยน</div>}/> <Route path="/register" element={<RegisterPage/>}/>
<Route path="/forgot-password" element={<div>หนาลมรหสผาน</div>}/> <Route path="/forgot-password" element={<div>หนาลมรหสผาน</div>}/>
</Route> </Route>

View File

@ -7,3 +7,14 @@ export const loginSchema = yup.object().shape({
username: yup.string().required('กรุณากรอกชื่อผู้ใช้งาน'), username: yup.string().required('กรุณากรอกชื่อผู้ใช้งาน'),
password: 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('กรุณายืนยันรหัสผ่าน'),
});

View File

@ -3,6 +3,7 @@ import { useDispatch } from 'react-redux';
import { loginSuccess } from '../features/auth/authSlice'; import { loginSuccess } from '../features/auth/authSlice';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import axios from 'axios'; // ใช้ Axios ธรรมดาสำหรับการเรียกที่ยังไม่มี Token import axios from 'axios'; // ใช้ Axios ธรรมดาสำหรับการเรียกที่ยังไม่มี Token
import { addToast } from '../features/toast/toastSlice';
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL; const API_BASE_URL = import.meta.env.VITE_API_BASE_URL;
@ -137,3 +138,55 @@ export const useLoginMutation = () => {
}, },
}); });
}; };
// ----------------------------------------------------
// 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);
},
});
};