「6」next-shopping:登录注册前端页面实现

作为表单页面,我们需要使用一些好东西来提升开发体验,一个是react-hook-form用于对表单的构建和数据收集,以及zod对表单进行校验,将他们结合在一起就能产生奇妙的化学反应,想要了解更多的话可以查看gayhub地址:github.com/react-hook-...

安装依赖:

shell 复制代码
pnpm i @hookform/resolvers react-hook-form zod

其他

我们先来修复一下app/store/slices/fetchApiSlice.js的问题:将mutation的方法的data入参改名为body

js 复制代码
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";

const fetchApi = createApi({
  reducerPath: "fetchApi",
  baseQuery: fetchBaseQuery({ baseUrl: process.env.BASE_URL }),
  endpoints: (builder) => ({
    getData: builder.query({
      query: ({ url, token }) => ({
        url,
        method: "GET",
        headers: { "Content-Type": "application/json", Authorization: token },
      }),
    }),

    postData: builder.mutation({
      query: ({ url, body, token }) => ({
        url,
        method: "POST",
        headers: { "Content-Type": "application/json", Authorization: token },
        body,
      }),
    }),

    patchData: builder.mutation({
      query: ({ url, body, token }) => ({
        url,
        method: "PATCH",
        headers: { "Content-Type": "application/json", Authorization: token },
        body,
      }),
    }),

    putData: builder.mutation({
      query: ({ url, body, token }) => ({
        url,
        method: "PUT",
        headers: { "Content-Type": "application/json", Authorization: token },
        body,
      }),
    }),

    deleteData: builder.mutation({
      query: ({ url, token }) => ({
        url,
        method: "DELETE",
        headers: { "Content-Type": "application/json", Authorization: token },
      }),
    }),
  }),
});

export const {
  useGetDataQuery,
  usePostDataMutation,
  usePatchDataMutation,
  usePutDataMutation,
  useDeleteDataMutation,
} = fetchApi;

export default fetchApi;

主要

由于我们的登录注册页面,是不需要HeaderFooter这些的,所以我们需要将这两个页面放在一个新的layout下面

新建app/(main)/(empty-layout)/layout.js,这样就有一个loginregister独享的布局

js 复制代码
export default function Layout({ children }) {
  return <>{children}</>
}

注册页面

新建app/(main)/(empty-layout)/register/page.js

js 复制代码
'use client'

import Image from 'next/image'
import Link from 'next/link'
import * as z from 'zod'
import { toast } from 'react-hot-toast'
import { useEffect } from 'react'
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { useDispatch } from 'react-redux'

import { usePostDataMutation } from '@/store/slices/fetchApiSlice'
import { userLogin } from '@/store/slices/authSlice'
import { DisplayError, Loading } from '@/components'

const schema = z
  .object({
    name: z
      .string({
        required_error: '用户名必填',
      })
      .min(3, { message: '用户名最少三个字符' }),
    email: z
      .string({
        required_error: '电子邮件地址必填',
      })
      .email('输入的电子邮件地址无效'),
    password: z
      .string({
        required_error: '密码必填',
      })
      .min(6, { message: '密码必须大于6个字符' }),
    confirmPassword: z
      .string({
        required_error: '确认密码必填',
      })
      .min(6, { message: '密码必须大于6个字符' }),
  })
  .refine(data => data.password === data.confirmPassword, {
    message: '确认密码必须与密码一致',
    path: ['confirmPassword'],
  })

export default function RegisterPage() {
  const dispatch = useDispatch()

  const [postData, { data, isSuccess, isError, isLoading, error }] = usePostDataMutation()

  const {
    register,
    handleSubmit,
    formState: { errors: formErrors },
    reset,
  } = useForm({
    resolver: zodResolver(schema),
  })

  useEffect(() => {
    if (isSuccess) {
      toast.success('注册成功')
      dispatch(userLogin(data.data))
      reset()
    }
    if (isError) {
      toast.error(error?.data?.err)
    }
  }, [isSuccess, isError])

  const submitHandler = async ({ name, email, password, confirmPassword }) => {
    if (name && email && password && confirmPassword) {
      postData({ url: '/api/auth/register', body: { name, email, password }, token: '' })
    }
  }

  return (
    <div className=" grid items-center min-h-screen ">
      <div className="container max-w-xl px-12 py-6 space-y-6 lg:border lg:border-gray-100 lg:rounded-lg lg:shadow">
        <div className="relative w-44 h-24 mx-auto">
          <Link passHref href="/">
            <Image src="/images/logo.svg" layout="fill" alt="logo" />
          </Link>
        </div>
        <h2>需要注册</h2>
        <form className="space-y-5" onSubmit={handleSubmit(submitHandler)}>
          <div>
            <input
              type="text"
              className="input"
              name="name"
              placeholder="名称"
              {...register('name')}
            />
            <DisplayError errors={formErrors.name} />
          </div>
          <div>
            <input
              className="input"
              type="text"
              placeholder="电子邮件地址"
              {...register('email')}
            />
            <DisplayError errors={formErrors.email} />
          </div>

          <div>
            <input className="input" type="password" placeholder="密码" {...register('password')} />
            <DisplayError errors={formErrors.password} />
          </div>

          <div>
            <input
              className="input"
              type="password"
              placeholder="重复密码"
              {...register('confirmPassword')}
            />
            <DisplayError errors={formErrors.confirmPassword} />
          </div>

          <button className="btn mx-auto w-60" type="submit" disabled={isLoading}>
            {isLoading ? <Loading /> : '注册'}
          </button>
        </form>

        <div>
          <p className="inline ml-2">已经拥有账户</p>
          <Link href="/login">
            <span className="text-blue-400 text-lg ">登录</span>
          </Link>
        </div>
      </div>
    </div>
  )
}

效果如下:

表单没有居中,且输入框,validateError信息都很丑,我们需要设置统一的样式来覆盖

首先是居中,找到根目录下tailwind.config.ts中修改container:{center: true}

现在居中了,然后我们需要修改input默认的样式,和表单校验错误提示信息

先来看看怎么改,添加自定义样式 - Tailwind CSS 中文网 (nodejs.cn)

可以看到如果是html元素的默认样式我们只需要写到@layer base里面,所以我们找到next初始化自带的global.css,可能在app/global.css

覆盖默认纯html元素样式:

css 复制代码
@tailwind base;
@tailwind components;
@tailwind utilities;

 @layer base {

  input {
    box-shadow: 0 0 0px 1000px #fafafa inset;
    -webkit-box-shadow: 0 0 0px 1000px #fafafa inset;
  }
  h2 {
    @apply text-xl;
  }
  p {
    @apply text-base;
  }
}

这次的重点是基于类的样式,所以需要在@layer components下面:

css 复制代码
@tailwind base;
@tailwind components;
@tailwind utilities;

 @layer base {

  input {
    box-shadow: 0 0 0px 1000px #fafafa inset;
    -webkit-box-shadow: 0 0 0px 1000px #fafafa inset;
  }
  h2 {
    @apply text-xl;
  }
  p {
    @apply text-base;
  }
}

@layer components {
  .input {
    @apply bg-zinc-50 w-full block
     outline-none py-2 px-3 rounded-md
     text-base lg:text-lg  
     border border-gray-200
   focus:border-blue-600 transition-colors;
  }

  .error-msg {
    @apply mt-1.5 inline-flex gap-x-1 justify-center items-center text-sm text-red-600;
  }

  .btn {
    @apply text-white bg-red-500 py-3 px-4 rounded-3xl block outline-none;
  }
}

效果如下:

试一试注册:我们注册成功了

并且我们在前端页面注册成功后dispatch了用户登录,可以看到浏览器cookies里面已经存了用户信息和token

登录页面

新建app/(main)/(empty-layout)/login/page.js

js 复制代码
'use client'

import { useEffect } from 'react'
import Link from 'next/link'
import Image from 'next/image'
import * as z from 'zod'
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { toast } from 'react-hot-toast'
import { useDispatch } from 'react-redux'

import { usePostDataMutation } from '@/store/slices/fetchApiSlice'
import { DisplayError, Loading } from '@/components'
import { userLogin } from '@/store/slices/authSlice'

const schema = z.object({
  email: z
    .string({
      required_error: '邮件地址必填',
    })
    .email('输入的电子邮件地址无效'),
  password: z
    .string({
      required_error: '密码必填',
    })
    .min(6, { message: '密码最少6个字符' }),
})

export default function LoginPage() {
  const dispatch = useDispatch()

  const [postData, { data, isSuccess, isError, isLoading, error }] = usePostDataMutation()

  const {
    handleSubmit,
    register,
    formState: { errors: formErrors },
    reset,
  } = useForm({
    resolver: zodResolver(schema),
  })

  useEffect(() => {
    if (isSuccess) {
      toast.success(data.msg)
      dispatch(userLogin(data.data))
      reset()
    }
    if (isError) {
      toast.error(error?.data.err)
    }
  }, [isSuccess, isError])

  const submitHandler = async ({ email, password }) => {
    if (email && password) {
      await postData({
        url: '/api/auth/login',
        body: { email, password },
        token: '',
      })
    }
  }

  return (
    <div className="grid items-center min-h-screen ">
      <div className="container max-w-xl px-12 py-6 space-y-6 lg:border lg:border-gray-100 lg:rounded-lg lg:shadow">
        <div className="relative w-44 h-24 mx-auto">
          <Link passHref href="/">
            <Image src="/images/logo.svg" layout="fill" alt="logo" />
          </Link>
        </div>
        <h2>登录</h2>
        <form className="space-y-5" onSubmit={handleSubmit(submitHandler)}>
          <div>
            <input
              className="input"
              type="text"
              placeholder="电子邮件地址"
              {...register('email')}
            />
            <DisplayError errors={formErrors.email} />
          </div>

          <div>
            <input className="input" type="password" placeholder="密码" {...register('password')} />
            <DisplayError errors={formErrors.password} />
          </div>

          <button className="btn mx-auto w-60" type="submit" disabled={isLoading}>
            {isLoading ? <Loading /> : '登录'}
          </button>
        </form>

        <div>
          <p className="inline ml-2">你还没有注册</p>
          <Link href="/register">
            <span className="text-blue-400 text-lg ">注册</span>
          </Link>
        </div>
      </div>
    </div>
  )
}

我们尝试一下登录:

非常棒,今天就到这儿了,摸鱼时间没有啦,需要忙工作😂,下次再见

代码地址:feat: 实现前端页面登录注册 · liyunfu1998/next-shopping@99721b2 (github.com)

相关推荐
yuanyxh2 小时前
Mac 软件推荐
前端·javascript·程序员
万少2 小时前
AtomCode开发微信小程序《谁去呀》 全流程
前端·javascript·后端
某人辛木2 小时前
Web自动化测试
前端·python·pycharm·pytest
Kagol3 小时前
Superpowers GSD gstack AgentSkills深度测评
前端·人工智能
pixcarp3 小时前
知识库系统的内容资产闭环怎么设计
服务器·数据库·后端·golang
JosieBook3 小时前
【数据库】时序预测能力的分级进化:TimechoAI如何让每一类用户都能精准预见未来
java·开发语言·数据库
excel3 小时前
JavaScript 字符串与模板字面量:从表象到本质理解
前端
京东云开发者4 小时前
当AI成为导演-如何用AI创作动漫短剧
前端
李白的天不白4 小时前
使用 SmartAdmin 进行前后端开发
java·前端
乘风gg4 小时前
🤡PUA AI Coding 工具 的 10 条终极语录
前端·ai编程·claude