소스 검색

refactor(dashboard): Address code review feedback

- Extract validation messages to VALIDATION_MESSAGES constant for consistency and future i18n
- Use object format { message: '...' } for Zod validation to match codebase conventions
- Add WCAG AA contrast comments explaining destructive color choices
- Extract generateCssVariables helper to eliminate code duplication (SonarQube)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Will Nahmens 5 일 전
부모
커밋
30e2aa84e6
2개의 변경된 파일35개의 추가작업 그리고 14개의 파일을 삭제
  1. 21 6
      packages/dashboard/src/lib/framework/form-engine/form-schema-tools.ts
  2. 14 8
      packages/dashboard/vite/vite-plugin-theme.ts

+ 21 - 6
packages/dashboard/src/lib/framework/form-engine/form-schema-tools.ts

@@ -14,6 +14,19 @@ import {
 } from '@/vdb/framework/form-engine/form-engine-types.js';
 import { z, ZodRawShape, ZodType, ZodTypeAny } from 'zod';
 
+/**
+ * Centralized validation messages for form schema generation.
+ * Provides consistent messaging across all form validations and enables future i18n support.
+ */
+const VALIDATION_MESSAGES = {
+    REQUIRED: 'This field is required',
+    pattern: (pattern: string) => `Value must match pattern: ${pattern}`,
+    min: (min: number) => `Value must be at least ${min}`,
+    max: (max: number) => `Value must be at most ${max}`,
+    dateAfter: (date: string) => `Date must be after ${date}`,
+    dateBefore: (date: string) => `Date must be before ${date}`,
+} as const;
+
 function mapGraphQLCustomFieldToConfig(field: StructField) {
     const { __typename, ...rest } = field;
     const baseConfig: CustomFieldConfig = {
@@ -88,8 +101,8 @@ function createDateValidationSchema(minDate: Date | undefined, maxDate: Date | u
 
     const dateMinString = minDate?.toLocaleDateString() ?? '';
     const dateMaxString = maxDate?.toLocaleDateString() ?? '';
-    const dateMinMessage = minDate ? `Date must be after ${dateMinString}` : '';
-    const dateMaxMessage = maxDate ? `Date must be before ${dateMaxString}` : '';
+    const dateMinMessage = minDate ? VALIDATION_MESSAGES.dateAfter(dateMinString) : '';
+    const dateMaxMessage = maxDate ? VALIDATION_MESSAGES.dateBefore(dateMaxString) : '';
 
     return baseSchema.refine(
         val => {
@@ -119,7 +132,7 @@ function createStringValidationSchema(pattern?: string | null): ZodType {
     let schema = z.string();
     if (pattern) {
         schema = schema.regex(new RegExp(pattern), {
-            message: `Value must match pattern: ${pattern}`,
+            message: VALIDATION_MESSAGES.pattern(pattern),
         });
     }
     return schema;
@@ -137,12 +150,12 @@ function createNumberValidationSchema(min?: number | null, max?: number | null):
     let schema = z.number();
     if (min != null) {
         schema = schema.min(min, {
-            message: `Value must be at least ${min}`,
+            message: VALIDATION_MESSAGES.min(min),
         });
     }
     if (max != null) {
         schema = schema.max(max, {
-            message: `Value must be at most ${max}`,
+            message: VALIDATION_MESSAGES.max(max),
         });
     }
     return schema;
@@ -388,7 +401,9 @@ export function getZodTypeFromField(field: FieldInfo): ZodTypeAny {
         case 'String':
         case 'ID':
         case 'DateTime':
-            zodType = field.nullable ? z.string() : z.string().min(1, 'This field is required');
+            zodType = field.nullable
+                ? z.string()
+                : z.string().min(1, { message: VALIDATION_MESSAGES.REQUIRED });
             break;
         case 'Int':
         case 'Float':

+ 14 - 8
packages/dashboard/vite/vite-plugin-theme.ts

@@ -67,6 +67,7 @@ const defaultVariables: ThemeVariables = {
         'muted-foreground': 'oklch(0.5517 0.0138 285.9385)',
         accent: 'oklch(0.9674 0.0013 286.3752)',
         'accent-foreground': 'oklch(0.2103 0.0059 285.8852)',
+        // L=0.60 ensures WCAG AA contrast ratio (4.5:1) against white backgrounds
         destructive: 'oklch(0.60 0.24 27.325)',
         'destructive-foreground': 'oklch(0.9851 0 0)',
         success: 'hsl(99deg 67.25% 33.2%)',
@@ -111,6 +112,7 @@ const defaultVariables: ThemeVariables = {
         'muted-foreground': 'oklch(0.7118 0.0129 286.0665)',
         accent: 'oklch(0.2739 0.0055 286.0326)',
         'accent-foreground': 'oklch(0.9851 0 0)',
+        // L=0.75 ensures WCAG AA contrast ratio (4.5:1) against dark backgrounds
         destructive: 'oklch(0.75 0.22 25)',
         'destructive-foreground': 'oklch(0.9851 0 0)',
         success: 'hsl(100 76.42% 22.21%)',
@@ -145,6 +147,16 @@ export type ThemeVariablesPluginOptions = {
     theme?: ThemeVariables;
 };
 
+/**
+ * Converts a theme colors object into CSS custom property declarations.
+ */
+function generateCssVariables(theme: ThemeColors): string {
+    return Object.entries(theme)
+        .filter(([_, value]) => value !== undefined)
+        .map(([key, value]) => `--${key}: ${value as string};`)
+        .join('\n');
+}
+
 export function themeVariablesPlugin(options: ThemeVariablesPluginOptions): Plugin {
     const virtualModuleId = 'virtual:admin-theme';
     const resolvedVirtualModuleId = `\0${virtualModuleId}`;
@@ -172,17 +184,11 @@ export function themeVariablesPlugin(options: ThemeVariablesPluginOptions): Plug
 
                 const themeCSS = `
                     :root {
-                        ${Object.entries(mergedLightTheme)
-                            .filter(([key, value]) => value !== undefined)
-                            .map(([key, value]) => `--${key}: ${value as string};`)
-                            .join('\n')}
+                        ${generateCssVariables(mergedLightTheme)}
                     }
 
                     .dark {
-                        ${Object.entries(mergedDarkTheme)
-                            .filter(([key, value]) => value !== undefined)
-                            .map(([key, value]) => `--${key}: ${value as string};`)
-                            .join('\n')}
+                        ${generateCssVariables(mergedDarkTheme)}
                     }
                 `;