Creating a mobile menu at the bottom of the app

This commit is contained in:
2026-09-14 19:19:09 +03:30
parent f176fde1eb
commit 76acf8d1d3
7 changed files with 487 additions and 89 deletions
+6 -3
View File
@@ -1,9 +1,9 @@
import { Type } from 'class-transformer';
import {
IsEmail,
IsIn,
IsNumber,
IsString,
Matches,
Max,
MaxLength,
Min,
@@ -14,8 +14,11 @@ const INVITE_ROLES = ['admin', 'sales', 'support'] as const;
const ALL_ROLES = ['owner', 'admin', 'sales', 'support'] as const;
export class InviteUserDto {
@IsEmail()
email!: string;
@IsString()
@Matches(/^09\d{9}$|^\+98\d{10}$|^98\d{10}$|^9\d{9}$/, {
message: 'شماره موبایل معتبر نیست',
})
mobile!: string;
@IsString()
@MinLength(2)
+39 -19
View File
@@ -8,8 +8,13 @@ import {
import type { InviteUserRequest, OrgUserDto, UserRole } from '@mento/shared';
import * as bcrypt from 'bcryptjs';
import { Prisma } from '@prisma/client';
import { normalizeMobile } from '../auth/auth.service';
import { PrismaService } from '../prisma/prisma.service';
function inviteEmailFromMobile(mobile: string): string {
return `${mobile}@users.mento.local`;
}
@Injectable()
export class UsersService {
constructor(private readonly prisma: PrismaService) {}
@@ -31,30 +36,43 @@ export class UsersService {
throw new ForbiddenException('فقط مالک یا مدیر می‌تواند کاربر اضافه کند');
}
const email = dto.email.trim().toLowerCase();
const existing = await this.prisma.user.findFirst({
where: { organizationId, email },
});
if (existing) {
throw new ConflictException('این ایمیل در سازمان شما وجود دارد');
const mobile = normalizeMobile(dto.mobile);
if (!mobile) {
throw new BadRequestException('شماره موبایل معتبر نیست');
}
const global = await this.prisma.user.findFirst({ where: { email } });
if (global) {
throw new ConflictException('این ایمیل قبلاً در سیستم ثبت شده است');
const existingMobile = await this.prisma.user.findFirst({ where: { mobile } });
if (existingMobile) {
throw new ConflictException('این شماره موبایل قبلاً ثبت شده است');
}
const email = inviteEmailFromMobile(mobile);
const existingEmail = await this.prisma.user.findFirst({
where: { organizationId, email },
});
if (existingEmail) {
throw new ConflictException('این شماره موبایل قبلاً ثبت شده است');
}
const passwordHash = await bcrypt.hash(dto.password, 10);
const row = await this.prisma.user.create({
data: {
organizationId,
email,
fullName: dto.fullName.trim(),
passwordHash,
role: dto.role,
},
});
return this.toDto(row);
try {
const row = await this.prisma.user.create({
data: {
organizationId,
email,
mobile,
fullName: dto.fullName.trim(),
passwordHash,
role: dto.role,
},
});
return this.toDto(row);
} catch (e) {
if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === 'P2002') {
throw new ConflictException('این شماره موبایل قبلاً ثبت شده است');
}
throw e;
}
}
async updateCommission(
@@ -122,6 +140,7 @@ export class UsersService {
private toDto(row: {
id: string;
email: string;
mobile?: string | null;
fullName: string;
role: UserRole;
commissionPercent?: { toString(): string } | number;
@@ -130,6 +149,7 @@ export class UsersService {
return {
id: row.id,
email: row.email,
mobile: row.mobile ?? null,
fullName: row.fullName,
role: row.role,
commissionPercent: Number(row.commissionPercent ?? 0),