RegistrationForm.tsx 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. "use client";
  2. import Image from "next/image";
  3. import Link from "next/link";
  4. import { SubmitHandler, useForm } from "react-hook-form";
  5. import InputText from "@components/common/form/Input";
  6. import { useCustomToast } from "@/utils/hooks/useToast";
  7. import { useRouter } from "next/navigation";
  8. import { EMAIL_REGEX, SIGNUP_IMG, IS_VALID_INPUT } from "@utils/constants";
  9. import { createUser } from "@utils/actions";
  10. import { Button } from "@components/common/button/Button";
  11. export type RegisterInputs = {
  12. firstName: string;
  13. lastName: string;
  14. email: string;
  15. password: string;
  16. passwordConfirmation: string;
  17. };
  18. export default function RegistrationForm() {
  19. const router = useRouter();
  20. const {
  21. register,
  22. handleSubmit,
  23. formState: { errors, isSubmitting },
  24. } = useForm<RegisterInputs>({
  25. mode: "onSubmit",
  26. reValidateMode: "onChange",
  27. });
  28. const { showToast } = useCustomToast();
  29. /**
  30. *
  31. * 浏览器端 RegistrationForm.onSubmit
  32. → 调用 createUser(data) ← 这是一个 Server Action
  33. → 发送 POST 请求到当前页面路由(或者由框架生成的端点)
  34. → 服务端执行 createUser 函数(访问 cookies、session、数据库等)
  35. → 返回结果给浏览器
  36. */
  37. const onSubmit: SubmitHandler<RegisterInputs> = async (data) => {
  38. console.log('register run where?--------');
  39. if (data.password !== data.passwordConfirmation) {
  40. showToast("The Passwords do not match.", "warning");
  41. return;
  42. }
  43. await createUser(data)
  44. .then((res) => {
  45. if (res?.success) {
  46. showToast("User created successfully", "success");
  47. router.replace("/customer/login");
  48. } else {
  49. showToast(res?.error?.message || "Failed to create user", "warning");
  50. }
  51. })
  52. .catch((error) => {
  53. showToast(error.message || "An error occurred", "warning");
  54. });
  55. };
  56. return (
  57. <div className="mt-5 md:my-8 md:mt-0 flex w-full items-center w-full max-w-screen-2xl mx-auto px-4 xss:px-7.5 justify-between gap-0 md:gap-4 lg:my-16 xl:my-28">
  58. <div className="relative flex w-full max-w-[583px] flex-col gap-y-4 lg:gap-y-12">
  59. <div className="font-outfit">
  60. <h2 className="py-1 text-2xl font-semibold sm:text-4xl">
  61. Become User
  62. </h2>
  63. <p className="mt-2 text-base md:text-lg font-normal text-black/[60%] dark:text-neutral-400 sm:mt-2">
  64. You are new to our store, we are glad to have you as a member.
  65. </p>
  66. </div>
  67. <form
  68. noValidate
  69. className="flex flex-col gap-y-5 lg:gap-y-12"
  70. onSubmit={handleSubmit(onSubmit)}
  71. >
  72. <div className="flex flex-col gap-y-2.5 lg:gap-[18px]">
  73. <div className="flex w-full gap-2.5 lg:gap-[18px]">
  74. <InputText
  75. {...register("firstName", {
  76. required: "First name is required",
  77. pattern: {
  78. value: IS_VALID_INPUT,
  79. message: "Invalid First Name",
  80. },
  81. })}
  82. className="w-full"
  83. errorMsg={
  84. errors.firstName?.message
  85. ? [errors.firstName.message]
  86. : undefined
  87. }
  88. label="First Name"
  89. labelPlacement="outside"
  90. name="firstName"
  91. placeholder="Enter first name"
  92. size="lg"
  93. />
  94. <InputText
  95. {...register("lastName",
  96. {
  97. required: "Last name is required",
  98. pattern: {
  99. value: IS_VALID_INPUT,
  100. message: "Invalid Last Name",
  101. },
  102. })}
  103. className="w-full"
  104. errorMsg={
  105. errors.lastName?.message
  106. ? [errors.lastName.message]
  107. : undefined
  108. }
  109. label="Last Name"
  110. labelPlacement="outside"
  111. name="lastName"
  112. placeholder="Enter last name"
  113. size="lg"
  114. />
  115. </div>
  116. <InputText
  117. {...register("email", {
  118. required: "Email is required",
  119. pattern: {
  120. value: EMAIL_REGEX,
  121. message: "Please enter a valid email.",
  122. },
  123. })}
  124. errorMsg={errors.email?.message}
  125. label="Email"
  126. labelPlacement="outside"
  127. name="email"
  128. placeholder="Enter email address"
  129. size="lg"
  130. />
  131. <InputText
  132. {...register("password", {
  133. required: "Password is required",
  134. minLength: {
  135. value: 8,
  136. message: "Must be at least 8 characters",
  137. },
  138. validate: (val) => {
  139. if (!/[A-Z]/.test(val))
  140. return "Must contain at least one uppercase letter";
  141. if (!/[a-z]/.test(val))
  142. return "Must contain at least one lowercase letter";
  143. if (!/[0-9]/.test(val))
  144. return "Must contain at least one number";
  145. if (/\s/.test(val)) return "Cannot contain spaces";
  146. return true;
  147. },
  148. })}
  149. label="Password"
  150. labelPlacement="outside"
  151. name="password"
  152. placeholder="Enter password"
  153. typeName="password"
  154. size="lg"
  155. errorMsg={
  156. errors.password?.message ? [errors.password.message] : undefined
  157. }
  158. />
  159. <InputText
  160. {...register("passwordConfirmation", {
  161. required: "Please confirm your password",
  162. })}
  163. label="Confirm Password"
  164. labelPlacement="outside"
  165. name="passwordConfirmation"
  166. placeholder="Enter confirm password"
  167. size="lg"
  168. typeName="password"
  169. />
  170. </div>
  171. <div className="flex flex-col gap-y-3 mb-8 lg:mb-0">
  172. <Button
  173. disabled={isSubmitting}
  174. loading={isSubmitting}
  175. title="Sign Up"
  176. type="submit"
  177. />
  178. <span className="mx-auto md:mx-0 font-outfit">
  179. Already have an account?{" "}
  180. <Link className="text-blue-600 underline" href="/customer/login" aria-label="Go to sign in page">
  181. Sign In
  182. </Link>
  183. </span>
  184. </div>
  185. </form>
  186. </div>
  187. <div className="relative hidden aspect-[0.9] max-h-[692px] w-full max-w-[790px] sm:block md:aspect-[1.14]">
  188. <Image
  189. fill
  190. priority
  191. alt="Sign Up Image"
  192. className="h-full w-full object-cover transition duration-300 ease-in-out group-hover:scale-105"
  193. sizes="(min-width: 768px) 66vw, 100vw"
  194. src={SIGNUP_IMG}
  195. />
  196. </div>
  197. </div>
  198. );
  199. }