พัฒนา Frontend หน้าลงทะเบียน (Registration Page)
This commit is contained in:
parent
3a4d6c245c
commit
248a31b13f
37
web/src/components/Registration/RegisterInfoCard.jsx
Normal file
37
web/src/components/Registration/RegisterInfoCard.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
83
web/src/pages/auth/RegisterPage.jsx
Normal file
83
web/src/pages/auth/RegisterPage.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@ -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 */}
|
||||
<Route element={<AuthLayout/>}>
|
||||
<Route path="/login" element={<LoginForm/>}/>
|
||||
<Route path="/register" element={<div>หน้าลงทะเบียน</div>}/>
|
||||
<Route path="/register" element={<RegisterPage/>}/>
|
||||
<Route path="/forgot-password" element={<div>หน้าลืมรหัสผ่าน</div>}/>
|
||||
</Route>
|
||||
|
||||
|
||||
@ -7,3 +7,14 @@ 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('กรุณายืนยันรหัสผ่าน'),
|
||||
});
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
},
|
||||
});
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user