Skip to content

Commit

Permalink
用户名&邮箱校验
Browse files Browse the repository at this point in the history
  • Loading branch information
sobird committed Jan 7, 2024
1 parent a01b191 commit a017d21
Show file tree
Hide file tree
Showing 11 changed files with 247 additions and 116 deletions.
31 changes: 21 additions & 10 deletions actions/user.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
'use server';

import { redirect } from 'next/navigation';
import { WhereOptions } from 'sequelize';
import { WhereOptions, Op } from 'sequelize';

import { revalidatePath } from 'next/cache';
import { UserModel } from '@/models';
import { generate, verify } from '@/lib/otp';
import { transporter } from '@/lib/mailer';
import reactToHtml from '@/lib/reactToHtml';
import CaptchaEmailBody from '@/components/email-template/captcha';
import {
SignUpZodWithRefine, SignUpAttributes, CreateUserZodWithRefine, CreateUserAttributes,
SignUpZod, SignUpAttributes, UserWithPasswordZod, UserAttributes, UserZod,
} from '@/zod/user';
import { ActionStatus } from '.';

Expand All @@ -25,7 +26,7 @@ type UserServerActionState = ServerActionState<SignUpAttributes>;
export async function signUpAction(
payload: SignUpAttributes,
): Promise<UserServerActionState> {
const validatedFields = await SignUpZodWithRefine.safeParseAsync(payload);
const validatedFields = await SignUpZod.safeParseAsync(payload);
if (!validatedFields.success) {
return {
status: ActionStatus.FAILURE,
Expand Down Expand Up @@ -57,9 +58,9 @@ export async function signUpAction(
}

export async function createUserAction(
payload: CreateUserAttributes,
payload: UserAttributes,
): Promise<UserServerActionState> {
const validatedFields = await CreateUserZodWithRefine.safeParseAsync(payload);
const validatedFields = await UserWithPasswordZod.safeParseAsync(payload);
if (!validatedFields.success) {
return {
status: ActionStatus.FAILURE,
Expand All @@ -80,10 +81,11 @@ export async function createUserAction(
redirect('/dashboard/user');
}

// 更新用户信息
export async function updateUserAction(
payload: CreateUserAttributes,
payload: UserAttributes,
): Promise<UserServerActionState> {
const validated = RoleFormZod.safeParse(payload);
const validated = UserZod.safeParse(payload);

if (!validated.success) {
return {
Expand All @@ -94,7 +96,7 @@ export async function updateUserAction(
}

try {
await RoleModel.update(validated.data, {
await UserModel.update(validated.data, {
where: {
id: payload.id,
},
Expand All @@ -103,7 +105,7 @@ export async function updateUserAction(
if (error.name === 'SequelizeUniqueConstraintError') {
return {
status: ActionStatus.FAILURE,
message: '角色名已存在',
message: '用户已存在',
};
}
}
Expand All @@ -118,7 +120,16 @@ export async function updateUserAction(
* @param where
* @returns
*/
export async function existsAction(where: WhereOptions) {
export async function existsAction(InternalWhere: WhereOptions, id?: number) {
let where = InternalWhere;
if (id) {
where = {
...InternalWhere,
id: {
[Op.ne]: id,
},
};
}
const result = await UserModel.findOne({ where });
return result !== null;
}
Expand Down
3 changes: 2 additions & 1 deletion app.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,6 @@ type ServerAction<Payload = unknown, State = ServerActionState> =
| ((payload: Payload) => Promise<State>);

type WithFormProps = {
mode?: 'create' | 'update' | 'detail'
mode?: 'create' | 'update' | 'detail';
data?: any;
};
6 changes: 3 additions & 3 deletions app/(authentication)/signup/form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
Input, Button, Form, ConfigProvider,
} from 'antd';
import { signUpAction } from '@/actions/user';
import { SignUpFormRule, UserPasswordRule } from '@/zod/user';
import { SignUpFormRule, passwordRule } from '@/zod/user';
import FieldCaptcha from '@/components/field-captcha';
import useServerAction from '@/hooks/useServerAction';
import { ActionStatus } from '@/actions';
Expand Down Expand Up @@ -38,10 +38,10 @@ export const SignupForm = () => {
>
<Input placeholder="用户名" />
</Form.Item>
<Form.Item name="password" rules={[UserPasswordRule]}>
<Form.Item name="password" rules={[passwordRule]}>
<Input.Password placeholder="登录密码" />
</Form.Item>
<Form.Item name="confirmPassword" dependencies={['password']} rules={[UserPasswordRule]}>
<Form.Item name="confirmPassword" dependencies={['password']} rules={[passwordRule]}>
<Input.Password placeholder="密码确认" />
</Form.Item>
<Form.Item name="email" validateDebounce={300} rules={[SignUpFormRule]}>
Expand Down
4 changes: 1 addition & 3 deletions app/dashboard/user/[id]/edit/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import { NextPage } from 'next';
import React from 'react';
import UserForm from '../../components/user-form';
import UserForm from '@/app/dashboard/user/components/user-form';
import { updateUserAction } from '@/actions/user';
import { UserModel, RoleModel } from '@/models';

Expand All @@ -31,8 +31,6 @@ const UserEditPage: NextPage<UserEditPageProps> = async ({ params }) => {
}],
});

console.log('users', user?.get());

return (
<UserForm action={updateUserAction} initialValues={user?.get()} mode="update" />
);
Expand Down
85 changes: 85 additions & 0 deletions app/dashboard/user/components/user-form/form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
'use client';

import React from 'react';
import {
Row, Col, Form, Input,
} from 'antd';
import {
UserFormRule, usernameRule, emailRule, passwordRule,
} from '@/zod/user';
import withActionForm from '@/components/with-action-form';
import DebounceSelect from '@/components/debounce-select';

const InternalUserForm: React.FC<WithFormProps> = ({ mode, data }) => {
const { roleOptions } = data;
return (
<Row gutter={[15, 0]}>
<Form.Item name="id">
<Input name="id" hidden />
</Form.Item>
<Col span={12}>
<Form.Item
name="username"
label="用户名"
validateDebounce={300}
rules={[usernameRule]}
required
>
<Input placeholder="用户登录名称" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
name="nickname"
label="用户昵称"
validateDebounce={300}
>
<Input placeholder="用户昵称" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="realname" label="真实姓名">
<Input placeholder="用户真实姓名" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="gender" label="性别">
<Input placeholder="性别" />
</Form.Item>
</Col>

{mode === 'create' && (
<>
<Col span={12}>
<Form.Item label="登录密码" name="password" rules={[passwordRule]} required>
<Input.Password placeholder="登录密码" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item label="密码确认" name="confirmPassword" dependencies={['password']} rules={[passwordRule]} required>
<Input.Password placeholder="密码确认" />
</Form.Item>
</Col>
</>
)}

<Col span={12}>
<Form.Item label="用户邮箱" name="email" validateDebounce={300} rules={[emailRule]} required>
<Input placeholder="邮箱" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item label="手机号码" name="mobile">
<Input placeholder="用户手机号码" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item label="角色" name="roles">
<DebounceSelect options={roleOptions} mode="multiple" placeholder="请选择" />
</Form.Item>
</Col>
</Row>
);
};

export default withActionForm(InternalUserForm);
84 changes: 15 additions & 69 deletions app/dashboard/user/components/user-form/index.tsx
Original file line number Diff line number Diff line change
@@ -1,74 +1,20 @@
'use client';
import React, { ComponentProps } from 'react';
import { RoleModel } from '@/models';
import InternalUserForm from './form';

import React from 'react';
import {
Row, Col, Form, Input,
} from 'antd';
import { CreateUserFormRule, UserPasswordRule } from '@/zod/user';
import withActionForm from '@/components/with-action-form';

const UserForm: React.FC<WithFormProps> = ({ mode }) => {
console.log('mode', mode);
const UserForm: React.FC<ComponentProps<typeof InternalUserForm>> = async (props) => {
const roleOptions = await RoleModel.findAll({
raw: true,
attributes: [['id', 'value'], ['name', 'label']],
});
return (
<Row gutter={[15, 0]}>
<Col span={12}>
<Form.Item
name="username"
label="用户名"
validateDebounce={300}
rules={[CreateUserFormRule]}
required
>
<Input placeholder="用户登录名称" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
name="nickname"
label="用户昵称"
validateDebounce={300}
>
<Input placeholder="用户昵称" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="realname" label="真实姓名">
<Input placeholder="用户真实姓名" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="gender" label="性别">
<Input placeholder="性别" />
</Form.Item>
</Col>

{mode === 'create' && (
<>
<Col span={12}>
<Form.Item label="登录密码" name="password" rules={[UserPasswordRule]}>
<Input.Password placeholder="登录密码" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item label="密码确认" name="confirmPassword" dependencies={['password']} rules={[UserPasswordRule]}>
<Input.Password placeholder="密码确认" />
</Form.Item>
</Col>
</>
)}

<Col span={12}>
<Form.Item label="用户邮箱" name="email" validateDebounce={300} rules={[CreateUserFormRule]}>
<Input placeholder="邮箱" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item label="手机号码" name="mobile">
<Input placeholder="用户手机号码" />
</Form.Item>
</Col>
</Row>
<InternalUserForm
{...props}
data={{
roleOptions,
}}
/>
);
};

export default withActionForm(UserForm);
export default UserForm;
2 changes: 1 addition & 1 deletion app/dashboard/user/create/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import { createUserAction } from '@/actions/user';
import UserForm from '../components/user-form';
import UserForm from '@/app/dashboard/user/components/user-form';

const Page = () => {
return (
Expand Down
65 changes: 65 additions & 0 deletions components/debounce-select/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import React, { useMemo, useRef, useState } from 'react';
import debounce from 'lodash/debounce';
import { Select, Spin } from 'antd';
import type { SelectProps } from 'antd/es/select';

export interface DebounceSelectProps<ValueType = any>
extends Omit<SelectProps<ValueType | ValueType[]>, 'children'> {
fetcher?: (search: string) => Promise<ValueType[]>;
delay?: number;
}

interface SelectValueType {
key?: string;
label: React.ReactNode;
value: string | number;
}

function DebounceSelect<ValueType extends SelectValueType = any>({
fetcher,
delay = 300,
options: InternalOptions = [],
...props
}: DebounceSelectProps<ValueType>) {
const [fetching, setFetching] = useState(false);
const [options, setOptions] = useState(InternalOptions);
const fetchRef = useRef(0);

const onSearch = useMemo(() => {
if (!fetcher) {
return;
}
const search = (value: string) => {
fetchRef.current += 1;
const fetchId = fetchRef.current;
// setOptions([]);
setFetching(true);

fetcher(value).then((newOptions) => {
if (fetchId !== fetchRef.current) {
// for fetch callback order
return;
}

setOptions(newOptions);
setFetching(false);
});
};

return debounce(search, delay);
}, [fetcher, delay]);

console.log('options', options);
return (
<Select
labelInValue
filterOption={false}
onSearch={onSearch}
notFoundContent={fetching ? <Spin size="small" /> : null}
{...props}
options={options}
/>
);
}

export default DebounceSelect;
Loading

0 comments on commit a017d21

Please sign in to comment.