feat: polish admin UI, phone login, and product details

Switch admin auth to mobile, restyle shell/login with Vuesax icons, and add product detail fields plus public product pages.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
S. Esmaeili
2026-10-22 17:18:17 +03:30
co-authored by Cursor
parent 789d224ea0
commit 49f30a96b1
54 changed files with 3632 additions and 574 deletions
+28 -3
View File
@@ -11,8 +11,33 @@ export const ROLE_LABELS: Record<Role, string> = {
const password = z.string().min(8, "رمز عبور باید حداقل ۸ کاراکتر باشد").max(128);
const FA_DIGITS = "۰۱۲۳۴۵۶۷۸۹";
const AR_DIGITS = "٠١٢٣٤٥٦٧٨٩";
/** Normalize Iranian mobile to `09XXXXXXXXX` (Latin digits). */
export function normalizeIrMobile(raw: string): string {
let s = raw.trim().replace(/[\s\-()]/g, "");
s = s.replace(/[۰-۹٠-٩]/g, (d) => {
const fa = FA_DIGITS.indexOf(d);
if (fa >= 0) return String(fa);
const ar = AR_DIGITS.indexOf(d);
return ar >= 0 ? String(ar) : d;
});
if (s.startsWith("+98")) s = `0${s.slice(3)}`;
else if (s.startsWith("0098")) s = `0${s.slice(4)}`;
else if (s.startsWith("98") && s.length === 12) s = `0${s.slice(2)}`;
return s;
}
export const irMobileSchema = z
.string()
.trim()
.min(1, "شماره موبایل را وارد کنید")
.transform(normalizeIrMobile)
.pipe(z.string().regex(/^09\d{9}$/, "شماره موبایل معتبر نیست (مثلاً ۰۹۱۲۱۲۳۴۵۶۷)"));
export const loginSchema = z.object({
email: z.email("ایمیل معتبر نیست").trim().toLowerCase(),
phone: irMobileSchema,
password: z.string().min(1, "رمز عبور را وارد کنید").max(128),
});
export type LoginInput = z.infer<typeof loginSchema>;
@@ -24,7 +49,7 @@ export const changePasswordSchema = z.object({
export type ChangePasswordInput = z.infer<typeof changePasswordSchema>;
export const userCreateSchema = z.object({
email: z.email("ایمیل معتبر نیست").trim().toLowerCase(),
phone: irMobileSchema,
name: req(80),
role: z.enum(ROLES),
password,
@@ -42,7 +67,7 @@ export type UserUpdateInput = z.infer<typeof userUpdateSchema>;
export interface AdminUserDto {
id: number;
email: string;
phone: string;
name: string;
role: Role;
isActive: boolean;