fix: Optimize flipping card animation backface and timing
This commit is contained in:
@@ -1,186 +1,186 @@
|
||||
/**
|
||||
* Zod Validation Schemas for API endpoints
|
||||
* Centralized validation logic for type-safety and security
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
|
||||
// ==========================================
|
||||
// QR Code Schemas
|
||||
// ==========================================
|
||||
|
||||
export const qrStyleSchema = z.object({
|
||||
fgColor: z.string().regex(/^#[0-9A-F]{6}$/i, 'Invalid foreground color format').optional(),
|
||||
bgColor: z.string().regex(/^#[0-9A-F]{6}$/i, 'Invalid background color format').optional(),
|
||||
cornerStyle: z.enum(['square', 'rounded']).optional(),
|
||||
size: z.number().min(100).max(1000).optional(),
|
||||
});
|
||||
|
||||
export const createQRSchema = z.object({
|
||||
title: z.string()
|
||||
.min(1, 'Title is required')
|
||||
.max(100, 'Title must be less than 100 characters'),
|
||||
|
||||
content: z.record(z.any()), // Accept any object structure for content
|
||||
|
||||
isStatic: z.boolean().optional(),
|
||||
|
||||
contentType: z.enum(['URL', 'VCARD', 'GEO', 'PHONE', 'SMS', 'WHATSAPP', 'TEXT', 'PDF', 'APP', 'COUPON', 'FEEDBACK'], {
|
||||
errorMap: () => ({ message: 'Invalid content type' })
|
||||
}),
|
||||
|
||||
tags: z.array(z.string()).optional(),
|
||||
|
||||
style: z.object({
|
||||
foregroundColor: z.string().optional(),
|
||||
backgroundColor: z.string().optional(),
|
||||
cornerStyle: z.enum(['square', 'rounded']).optional(),
|
||||
size: z.number().optional(),
|
||||
}).optional(),
|
||||
});
|
||||
|
||||
export const updateQRSchema = z.object({
|
||||
title: z.string()
|
||||
.min(1, 'Title is required')
|
||||
.max(100, 'Title must be less than 100 characters')
|
||||
.optional(),
|
||||
|
||||
content: z.string()
|
||||
.min(1, 'Content is required')
|
||||
.max(5000, 'Content must be less than 5000 characters')
|
||||
.optional(),
|
||||
|
||||
style: qrStyleSchema.optional(),
|
||||
|
||||
isActive: z.boolean().optional(),
|
||||
});
|
||||
|
||||
export const bulkQRSchema = z.object({
|
||||
qrs: z.array(
|
||||
z.object({
|
||||
title: z.string().min(1).max(100),
|
||||
content: z.string().min(1).max(5000),
|
||||
contentType: z.enum(['URL', 'VCARD', 'GEO', 'PHONE', 'SMS', 'WHATSAPP', 'TEXT', 'PDF', 'APP', 'COUPON', 'FEEDBACK']),
|
||||
})
|
||||
).min(1, 'At least one QR code is required')
|
||||
.max(100, 'Maximum 100 QR codes per bulk creation'),
|
||||
});
|
||||
|
||||
// ==========================================
|
||||
// Authentication Schemas
|
||||
// ==========================================
|
||||
|
||||
export const loginSchema = z.object({
|
||||
email: z.string()
|
||||
.email('Invalid email format')
|
||||
.toLowerCase(),
|
||||
|
||||
password: z.string()
|
||||
.min(1, 'Password is required'),
|
||||
});
|
||||
|
||||
export const signupSchema = z.object({
|
||||
name: z.string()
|
||||
.min(2, 'Name must be at least 2 characters')
|
||||
.max(100, 'Name must be less than 100 characters')
|
||||
.trim(),
|
||||
|
||||
email: z.string()
|
||||
.email('Invalid email format')
|
||||
.toLowerCase()
|
||||
.trim(),
|
||||
|
||||
password: z.string()
|
||||
.min(8, 'Password must be at least 8 characters')
|
||||
.max(100, 'Password must be less than 100 characters'),
|
||||
// Password complexity rules removed for easier testing
|
||||
});
|
||||
|
||||
export const forgotPasswordSchema = z.object({
|
||||
email: z.string()
|
||||
.email('Invalid email format')
|
||||
.toLowerCase()
|
||||
.trim(),
|
||||
});
|
||||
|
||||
export const resetPasswordSchema = z.object({
|
||||
token: z.string().min(1, 'Reset token is required'),
|
||||
password: z.string()
|
||||
.min(8, 'Password must be at least 8 characters')
|
||||
.max(100, 'Password must be less than 100 characters'),
|
||||
// Password complexity rules removed for easier testing
|
||||
});
|
||||
|
||||
// ==========================================
|
||||
// Settings Schemas
|
||||
// ==========================================
|
||||
|
||||
export const updateProfileSchema = z.object({
|
||||
name: z.string()
|
||||
.min(2, 'Name must be at least 2 characters')
|
||||
.max(100, 'Name must be less than 100 characters')
|
||||
.trim(),
|
||||
});
|
||||
|
||||
export const changePasswordSchema = z.object({
|
||||
currentPassword: z.string()
|
||||
.min(1, 'Current password is required'),
|
||||
|
||||
newPassword: z.string()
|
||||
.min(8, 'Password must be at least 8 characters')
|
||||
.max(100, 'Password must be less than 100 characters'),
|
||||
// Password complexity rules removed for easier testing
|
||||
});
|
||||
|
||||
// ==========================================
|
||||
// Stripe Schemas
|
||||
// ==========================================
|
||||
|
||||
export const createCheckoutSchema = z.object({
|
||||
priceId: z.string().min(1, 'Price ID is required'),
|
||||
});
|
||||
|
||||
// ==========================================
|
||||
// Newsletter Schemas
|
||||
// ==========================================
|
||||
|
||||
export const newsletterSubscribeSchema = z.object({
|
||||
email: z.string()
|
||||
.email('Invalid email format')
|
||||
.toLowerCase()
|
||||
.trim()
|
||||
.max(255, 'Email must be less than 255 characters'),
|
||||
});
|
||||
|
||||
// ==========================================
|
||||
// Helper: Format Zod Errors
|
||||
// ==========================================
|
||||
|
||||
export function formatZodError(error: z.ZodError) {
|
||||
return {
|
||||
error: 'Validation failed',
|
||||
details: error.errors.map(err => ({
|
||||
field: err.path.join('.'),
|
||||
message: err.message,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// Helper: Validate with Zod
|
||||
// ==========================================
|
||||
|
||||
export async function validateRequest<T>(
|
||||
schema: z.ZodSchema<T>,
|
||||
data: unknown
|
||||
): Promise<{ success: true; data: T } | { success: false; error: any }> {
|
||||
try {
|
||||
const validatedData = schema.parse(data);
|
||||
return { success: true, data: validatedData };
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
return { success: false, error: formatZodError(error) };
|
||||
}
|
||||
return { success: false, error: { error: 'Invalid request data' } };
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Zod Validation Schemas for API endpoints
|
||||
* Centralized validation logic for type-safety and security
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
|
||||
// ==========================================
|
||||
// QR Code Schemas
|
||||
// ==========================================
|
||||
|
||||
export const qrStyleSchema = z.object({
|
||||
fgColor: z.string().regex(/^#[0-9A-F]{6}$/i, 'Invalid foreground color format').optional(),
|
||||
bgColor: z.string().regex(/^#[0-9A-F]{6}$/i, 'Invalid background color format').optional(),
|
||||
cornerStyle: z.enum(['square', 'rounded']).optional(),
|
||||
size: z.number().min(100).max(1000).optional(),
|
||||
});
|
||||
|
||||
export const createQRSchema = z.object({
|
||||
title: z.string()
|
||||
.min(1, 'Title is required')
|
||||
.max(100, 'Title must be less than 100 characters'),
|
||||
|
||||
content: z.record(z.any()), // Accept any object structure for content
|
||||
|
||||
isStatic: z.boolean().optional(),
|
||||
|
||||
contentType: z.enum(['URL', 'VCARD', 'GEO', 'PHONE', 'SMS', 'WHATSAPP', 'TEXT', 'PDF', 'APP', 'COUPON', 'FEEDBACK'], {
|
||||
errorMap: () => ({ message: 'Invalid content type' })
|
||||
}),
|
||||
|
||||
tags: z.array(z.string()).optional(),
|
||||
|
||||
style: z.object({
|
||||
foregroundColor: z.string().optional(),
|
||||
backgroundColor: z.string().optional(),
|
||||
cornerStyle: z.enum(['square', 'rounded']).optional(),
|
||||
size: z.number().optional(),
|
||||
}).optional(),
|
||||
});
|
||||
|
||||
export const updateQRSchema = z.object({
|
||||
title: z.string()
|
||||
.min(1, 'Title is required')
|
||||
.max(100, 'Title must be less than 100 characters')
|
||||
.optional(),
|
||||
|
||||
content: z.string()
|
||||
.min(1, 'Content is required')
|
||||
.max(5000, 'Content must be less than 5000 characters')
|
||||
.optional(),
|
||||
|
||||
style: qrStyleSchema.optional(),
|
||||
|
||||
isActive: z.boolean().optional(),
|
||||
});
|
||||
|
||||
export const bulkQRSchema = z.object({
|
||||
qrs: z.array(
|
||||
z.object({
|
||||
title: z.string().min(1).max(100),
|
||||
content: z.string().min(1).max(5000),
|
||||
contentType: z.enum(['URL', 'VCARD', 'GEO', 'PHONE', 'SMS', 'WHATSAPP', 'TEXT', 'PDF', 'APP', 'COUPON', 'FEEDBACK']),
|
||||
})
|
||||
).min(1, 'At least one QR code is required')
|
||||
.max(100, 'Maximum 100 QR codes per bulk creation'),
|
||||
});
|
||||
|
||||
// ==========================================
|
||||
// Authentication Schemas
|
||||
// ==========================================
|
||||
|
||||
export const loginSchema = z.object({
|
||||
email: z.string()
|
||||
.email('Invalid email format')
|
||||
.toLowerCase(),
|
||||
|
||||
password: z.string()
|
||||
.min(1, 'Password is required'),
|
||||
});
|
||||
|
||||
export const signupSchema = z.object({
|
||||
name: z.string()
|
||||
.min(2, 'Name must be at least 2 characters')
|
||||
.max(100, 'Name must be less than 100 characters')
|
||||
.trim(),
|
||||
|
||||
email: z.string()
|
||||
.email('Invalid email format')
|
||||
.toLowerCase()
|
||||
.trim(),
|
||||
|
||||
password: z.string()
|
||||
.min(8, 'Password must be at least 8 characters')
|
||||
.max(100, 'Password must be less than 100 characters'),
|
||||
// Password complexity rules removed for easier testing
|
||||
});
|
||||
|
||||
export const forgotPasswordSchema = z.object({
|
||||
email: z.string()
|
||||
.email('Invalid email format')
|
||||
.toLowerCase()
|
||||
.trim(),
|
||||
});
|
||||
|
||||
export const resetPasswordSchema = z.object({
|
||||
token: z.string().min(1, 'Reset token is required'),
|
||||
password: z.string()
|
||||
.min(8, 'Password must be at least 8 characters')
|
||||
.max(100, 'Password must be less than 100 characters'),
|
||||
// Password complexity rules removed for easier testing
|
||||
});
|
||||
|
||||
// ==========================================
|
||||
// Settings Schemas
|
||||
// ==========================================
|
||||
|
||||
export const updateProfileSchema = z.object({
|
||||
name: z.string()
|
||||
.min(2, 'Name must be at least 2 characters')
|
||||
.max(100, 'Name must be less than 100 characters')
|
||||
.trim(),
|
||||
});
|
||||
|
||||
export const changePasswordSchema = z.object({
|
||||
currentPassword: z.string()
|
||||
.min(1, 'Current password is required'),
|
||||
|
||||
newPassword: z.string()
|
||||
.min(8, 'Password must be at least 8 characters')
|
||||
.max(100, 'Password must be less than 100 characters'),
|
||||
// Password complexity rules removed for easier testing
|
||||
});
|
||||
|
||||
// ==========================================
|
||||
// Stripe Schemas
|
||||
// ==========================================
|
||||
|
||||
export const createCheckoutSchema = z.object({
|
||||
priceId: z.string().min(1, 'Price ID is required'),
|
||||
});
|
||||
|
||||
// ==========================================
|
||||
// Newsletter Schemas
|
||||
// ==========================================
|
||||
|
||||
export const newsletterSubscribeSchema = z.object({
|
||||
email: z.string()
|
||||
.email('Invalid email format')
|
||||
.toLowerCase()
|
||||
.trim()
|
||||
.max(255, 'Email must be less than 255 characters'),
|
||||
});
|
||||
|
||||
// ==========================================
|
||||
// Helper: Format Zod Errors
|
||||
// ==========================================
|
||||
|
||||
export function formatZodError(error: z.ZodError) {
|
||||
return {
|
||||
error: 'Validation failed',
|
||||
details: error.errors.map(err => ({
|
||||
field: err.path.join('.'),
|
||||
message: err.message,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// Helper: Validate with Zod
|
||||
// ==========================================
|
||||
|
||||
export async function validateRequest<T>(
|
||||
schema: z.ZodSchema<T>,
|
||||
data: unknown
|
||||
): Promise<{ success: true; data: T } | { success: false; error: any }> {
|
||||
try {
|
||||
const validatedData = schema.parse(data);
|
||||
return { success: true, data: validatedData };
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
return { success: false, error: formatZodError(error) };
|
||||
}
|
||||
return { success: false, error: { error: 'Invalid request data' } };
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user