feat: enhance party user schema with company, status, role and verification fields, update seeding and frontend form
This commit is contained in:
@@ -1279,4 +1279,8 @@ model PartyUser {
|
||||
sessions Session[]
|
||||
info Json?
|
||||
phoneNumber String @default("")
|
||||
company String @default("")
|
||||
status String @default("pending")
|
||||
role String @default("")
|
||||
isVerified Boolean @default(false)
|
||||
}
|
||||
|
@@ -1,6 +1,41 @@
|
||||
import { faker as enFaker } from '@faker-js/faker/locale/en_US';
|
||||
import { faker as zhFaker } from '@faker-js/faker/locale/zh_CN';
|
||||
import { faker as jaFaker } from '@faker-js/faker/locale/ja';
|
||||
import { faker as koFaker } from '@faker-js/faker/locale/ko';
|
||||
import { faker as twFaker } from '@faker-js/faker/locale/zh_TW';
|
||||
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
const ROLE = [
|
||||
`CEO`,
|
||||
`CTO`,
|
||||
`Project Coordinator`,
|
||||
`Team Leader`,
|
||||
`Software Developer`,
|
||||
`Marketing Strategist`,
|
||||
`Data Analyst`,
|
||||
`Product Owner`,
|
||||
`Graphic Designer`,
|
||||
`Operations Manager`,
|
||||
`Customer Support Specialist`,
|
||||
`Sales Manager`,
|
||||
`HR Recruiter`,
|
||||
`Business Consultant`,
|
||||
`Financial Planner`,
|
||||
`Network Engineer`,
|
||||
`Content Creator`,
|
||||
`Quality Assurance Tester`,
|
||||
`Public Relations Officer`,
|
||||
`IT Administrator`,
|
||||
`Compliance Officer`,
|
||||
`Event Planner`,
|
||||
`Legal Counsel`,
|
||||
`Training Coordinator`,
|
||||
];
|
||||
|
||||
const STATUS = ['active', 'pending', 'banned'];
|
||||
|
||||
async function partyUser() {
|
||||
const alice = await prisma.partyUser.upsert({
|
||||
where: { email: 'alice@prisma.io' },
|
||||
@@ -8,8 +43,14 @@ async function partyUser() {
|
||||
create: {
|
||||
email: 'alice@prisma.io',
|
||||
name: 'Alice',
|
||||
username: 'pualice',
|
||||
password: 'Aa12345678',
|
||||
emailVerified: new Date(),
|
||||
phoneNumber: '+85291234567',
|
||||
company: 'helloworld company',
|
||||
status: STATUS[0],
|
||||
role: ROLE[0],
|
||||
isVerified: true,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -19,31 +60,48 @@ async function partyUser() {
|
||||
create: {
|
||||
email: 'demo@minimals.cc',
|
||||
name: 'Demo',
|
||||
username: 'pudemo',
|
||||
password: '@2Minimal',
|
||||
emailVerified: new Date(),
|
||||
phoneNumber: '+85291234568',
|
||||
company: 'helloworld company',
|
||||
status: STATUS[1],
|
||||
role: ROLE[1],
|
||||
isVerified: true,
|
||||
},
|
||||
});
|
||||
|
||||
await prisma.partyUser.upsert({
|
||||
where: { email: 'bob@prisma.io' },
|
||||
update: {},
|
||||
create: {
|
||||
email: 'bob@prisma.io',
|
||||
name: 'Bob',
|
||||
password: 'Aa12345678',
|
||||
emailVerified: new Date(),
|
||||
},
|
||||
});
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const CJK_LOCALES = {
|
||||
en: enFaker,
|
||||
zh: zhFaker,
|
||||
ja: jaFaker,
|
||||
ko: koFaker,
|
||||
tw: twFaker,
|
||||
};
|
||||
|
||||
function getRandomCJKFaker() {
|
||||
const locales = Object.keys(CJK_LOCALES);
|
||||
const randomKey = locales[Math.floor(Math.random() * locales.length)] as keyof typeof CJK_LOCALES;
|
||||
return CJK_LOCALES[randomKey];
|
||||
}
|
||||
|
||||
const randomFaker = getRandomCJKFaker();
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
await prisma.partyUser.upsert({
|
||||
where: { email: `bob${i}@prisma.io` },
|
||||
where: { email: `party_user${i}@prisma.io` },
|
||||
update: {},
|
||||
create: {
|
||||
email: `bob${i}@prisma.io`,
|
||||
name: 'Bob',
|
||||
email: `party_user${i}@prisma.io`,
|
||||
name: `Party Dummy ${i}`,
|
||||
username: `pu${i.toString()}`,
|
||||
password: 'Aa12345678',
|
||||
emailVerified: new Date(),
|
||||
phoneNumber: `+8529123456${i.toString()}`,
|
||||
company: randomFaker.company.name(),
|
||||
role: ROLE[Math.floor(Math.random() * ROLE.length)],
|
||||
status: STATUS[Math.floor(Math.random() * STATUS.length)],
|
||||
isVerified: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@@ -7,7 +7,8 @@ clear
|
||||
while true; do
|
||||
yarn db:studio &
|
||||
|
||||
npx nodemon --ext ts,tsx,prisma --exec "yarn db:push && yarn seed && yarn dev"
|
||||
npx nodemon --ext ts,tsx,prisma --exec "yarn dev"
|
||||
# npx nodemon --ext ts,tsx,prisma --exec "yarn db:push && yarn seed && yarn dev"
|
||||
# yarn dev
|
||||
|
||||
killall node
|
||||
|
@@ -5,10 +5,10 @@ Content-Type: application/json
|
||||
|
||||
{
|
||||
"partyUserData": {
|
||||
"id": "cmbxzds7q0009xyn5367ifizp",
|
||||
"id": "cmc0cedkx000boln3viy77598",
|
||||
"createdAt": "2025-06-15T17:47:24.547Z",
|
||||
"updatedAt": "2025-06-15T17:47:24.547Z",
|
||||
"name": "Alice",
|
||||
"name": "Alice 123321",
|
||||
"username": null,
|
||||
"email": "alice@prisma.io",
|
||||
"emailVerified": "2025-06-15T17:47:23.919Z",
|
||||
@@ -16,6 +16,7 @@ Content-Type: application/json
|
||||
"image": null,
|
||||
"bucketImage": null,
|
||||
"admin": false,
|
||||
"info": null
|
||||
"info": null,
|
||||
"phoneNumber": "+85291234567"
|
||||
}
|
||||
}
|
||||
|
@@ -113,18 +113,29 @@ type SaveUserData = {
|
||||
password: string;
|
||||
};
|
||||
|
||||
export async function saveUser(userId: string, saveUserData: SaveUserData) {
|
||||
// const url = userId ? [endpoints.user.details, { params: { userId } }] : '';
|
||||
export async function updatePartyUser(partyUserData: Partial<IPartyUserItem>) {
|
||||
/**
|
||||
* Work on server
|
||||
*/
|
||||
const data = { partyUserData };
|
||||
await axiosInstance.put(endpoints.partyUser.update, data);
|
||||
|
||||
const res = await axiosInstance.post(
|
||||
//
|
||||
`http://localhost:7272/api/user/saveUser?userId=${userId}`,
|
||||
{
|
||||
data: saveUserData,
|
||||
}
|
||||
/**
|
||||
* Work in local
|
||||
*/
|
||||
mutate(
|
||||
endpoints.partyUser.list,
|
||||
(currentData: any) => {
|
||||
const currentPartyUsers: IPartyUserItem[] = currentData?.partyUsers;
|
||||
|
||||
const partyUsers = currentPartyUsers.map((partyUser) =>
|
||||
partyUser.id === partyUserData.id ? { ...partyUser, ...partyUserData } : partyUser
|
||||
);
|
||||
|
||||
return { ...currentData, partyUsers };
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
export async function uploadUserImage(saveUserData: SaveUserData) {
|
||||
|
@@ -11,7 +11,7 @@ import { useState } from 'react';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { isValidPhoneNumber } from 'react-phone-number-input/input';
|
||||
import { createUser, deletePartyUser, saveUser } from 'src/actions/party-user';
|
||||
import { createUser, deletePartyUser, updatePartyUser } from 'src/actions/party-user';
|
||||
import { Field, Form, schemaHelper } from 'src/components/hook-form';
|
||||
import { Label } from 'src/components/label';
|
||||
import { toast } from 'src/components/snackbar';
|
||||
@@ -27,27 +27,30 @@ import { z as zod } from 'zod';
|
||||
export type NewUserSchemaType = zod.infer<typeof NewUserSchema>;
|
||||
|
||||
export const NewUserSchema = zod.object({
|
||||
name: zod.string().min(1, { message: 'Name is required!' }),
|
||||
city: zod.string().min(1, { message: 'City is required!' }),
|
||||
name: zod.string().min(1, { message: 'Name is required!' }).optional().or(zod.literal('')),
|
||||
city: zod.string().min(1, { message: 'City is required!' }).optional().or(zod.literal('')),
|
||||
role: zod.string().min(1, { message: 'Role is required!' }),
|
||||
email: zod
|
||||
.string()
|
||||
.min(1, { message: 'Email is required!' })
|
||||
.email({ message: 'Email must be a valid email address!' }),
|
||||
state: zod.string().min(1, { message: 'State is required!' }),
|
||||
state: zod.string().min(1, { message: 'State is required!' }).optional().or(zod.literal('')),
|
||||
status: zod.string(),
|
||||
address: zod.string().min(1, { message: 'Address is required!' }),
|
||||
country: schemaHelper.nullableInput(zod.string().min(1, { message: 'Country is required!' }), {
|
||||
// message for null value
|
||||
message: 'Country is required!',
|
||||
}),
|
||||
zipCode: zod.string().min(1, { message: 'Zip code is required!' }),
|
||||
company: zod.string().min(1, { message: 'Company is required!' }),
|
||||
avatarUrl: schemaHelper.file({ message: 'Avatar is required!' }),
|
||||
phoneNumber: schemaHelper.phoneNumber({ isValid: isValidPhoneNumber }),
|
||||
isVerified: zod.boolean(),
|
||||
username: zod.string(),
|
||||
password: zod.string(),
|
||||
address: zod.string().min(1, { message: 'Address is required!' }).optional().or(zod.literal('')),
|
||||
country: schemaHelper
|
||||
.nullableInput(zod.string().min(1, { message: 'Country is required!' }), {
|
||||
// message for null value
|
||||
message: 'Country is required!',
|
||||
})
|
||||
.optional()
|
||||
.or(zod.literal('')),
|
||||
zipCode: zod.string().min(1, { message: 'Zip code is required!' }).optional().or(zod.literal('')),
|
||||
company: zod.string().min(1, { message: 'Company is required!' }).optional().or(zod.literal('')),
|
||||
avatarUrl: zod.string().optional().or(zod.literal('')),
|
||||
phoneNumber: zod.string().optional().or(zod.literal('')),
|
||||
isVerified: zod.boolean().default(true),
|
||||
username: zod.string().optional().or(zod.literal('')),
|
||||
password: zod.string().optional().or(zod.literal('')),
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
@@ -63,18 +66,19 @@ export function PartyUserNewEditForm({ currentUser }: Props) {
|
||||
|
||||
const defaultValues: NewUserSchemaType = {
|
||||
status: '',
|
||||
avatarUrl: null,
|
||||
isVerified: true,
|
||||
avatarUrl: '',
|
||||
name: '新用戶名字',
|
||||
email: 'user@123.com',
|
||||
phoneNumber: '+85291234567',
|
||||
country: 'Hong Kong',
|
||||
state: 'HK',
|
||||
city: 'hong kong',
|
||||
address: 'Kwun Tong, Sau Mau Ping',
|
||||
zipCode: '00000',
|
||||
company: 'test company',
|
||||
phoneNumber: '',
|
||||
country: '',
|
||||
state: '',
|
||||
city: '',
|
||||
address: '',
|
||||
zipCode: '',
|
||||
company: '',
|
||||
role: 'user',
|
||||
// Email is verified
|
||||
isVerified: true,
|
||||
//
|
||||
username: '',
|
||||
password: '',
|
||||
@@ -123,12 +127,14 @@ export function PartyUserNewEditForm({ currentUser }: Props) {
|
||||
data.avatarUrl = await fileToBase64(temp);
|
||||
}
|
||||
|
||||
const sanitizedValues: IPartyUserItem = values as unknown as IPartyUserItem;
|
||||
|
||||
if (currentUser) {
|
||||
// perform save
|
||||
await saveUser(currentUser.id, data);
|
||||
// perform
|
||||
await updatePartyUser(sanitizedValues);
|
||||
} else {
|
||||
// perform create
|
||||
await createUser(data);
|
||||
await createUser(sanitizedValues);
|
||||
}
|
||||
|
||||
toast.success(currentUser ? t('Update success!') : t('Create success!'));
|
||||
@@ -260,6 +266,9 @@ export function PartyUserNewEditForm({ currentUser }: Props) {
|
||||
gridTemplateColumns: { xs: 'repeat(1, 1fr)', sm: 'repeat(2, 1fr)' },
|
||||
}}
|
||||
>
|
||||
<Field.Text name="username" label={t('username')} />
|
||||
<Field.Text name="password" label={t('password')} />
|
||||
|
||||
<Field.Text name="name" label={t('Full name')} />
|
||||
<Field.Text name="email" label={t('Email address')} />
|
||||
<Field.Phone name="phoneNumber" label={t('Phone number')} country="HK" />
|
||||
@@ -274,19 +283,20 @@ export function PartyUserNewEditForm({ currentUser }: Props) {
|
||||
<Field.Text name="state" label={t('State/region')} />
|
||||
<Field.Text name="city" label={t('City')} />
|
||||
<Field.Text name="address" label={t('Address')} />
|
||||
<Field.Text name="zipCode" label={t('Zip/code')} />
|
||||
<Field.Text name="zipCode" label={t('Zip/code')} required={false} />
|
||||
<Field.Text name="company" label={t('Company')} />
|
||||
<Field.Text name="role" label={t('Role')} />
|
||||
</Box>
|
||||
|
||||
<Stack sx={{ mt: 3, alignItems: 'flex-end' }}>
|
||||
<>{JSON.stringify({ errors })}</>
|
||||
<Button
|
||||
disabled={isSubmitting}
|
||||
loading={isSubmitting}
|
||||
type="submit"
|
||||
variant="contained"
|
||||
>
|
||||
{!currentUser ? t('Create user') : t('Save changes')}
|
||||
{!currentUser ? t('create-user') : t('save-changes')}
|
||||
</Button>
|
||||
</Stack>
|
||||
</Card>
|
||||
|
Reference in New Issue
Block a user