prowler-ui

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Related Generic Skills

相关通用技能

  • typescript
    - Const types, flat interfaces
  • react-19
    - No useMemo/useCallback, compiler
  • nextjs-15
    - App Router, Server Actions
  • tailwind-4
    - cn() utility, styling rules
  • zod-4
    - Schema validation
  • zustand-5
    - State management
  • ai-sdk-5
    - Chat/AI features
  • playwright
    - E2E testing (see also
    prowler-test-ui
    )
  • typescript
    - 常量类型、扁平化接口
  • react-19
    - 无需useMemo/useCallback、编译器特性
  • nextjs-15
    - App Router、Server Actions
  • tailwind-4
    - cn()工具函数、样式规则
  • zod-4
    - Schema验证
  • zustand-5
    - 状态管理
  • ai-sdk-5
    - 聊天/AI功能
  • playwright
    - E2E测试(另见
    prowler-test-ui

Tech Stack (Versions)

技术栈(版本)

Next.js 15.5.9 | React 19.2.2 | Tailwind 4.1.13 | shadcn/ui
Zod 4.1.11 | React Hook Form 7.62.0 | Zustand 5.0.8
NextAuth 5.0.0-beta.30 | Recharts 2.15.4
HeroUI 2.8.4 (LEGACY - do not add new components)
Next.js 15.5.9 | React 19.2.2 | Tailwind 4.1.13 | shadcn/ui
Zod 4.1.11 | React Hook Form 7.62.0 | Zustand 5.0.8
NextAuth 5.0.0-beta.30 | Recharts 2.15.4
HeroUI 2.8.4 (LEGACY - 请勿新增组件)

CRITICAL: Component Library Rule

重要:组件库规则

  • ALWAYS: Use
    shadcn/ui
    + Tailwind (
    components/shadcn/
    )
  • NEVER: Add new HeroUI components (
    components/ui/
    is legacy only)
  • 必须:使用
    shadcn/ui
    + Tailwind(
    components/shadcn/
    目录)
  • 禁止:新增HeroUI组件(
    components/ui/
    仅为遗留目录)

DECISION TREES

决策树

Component Placement

组件放置规则

New feature UI? → shadcn/ui + Tailwind
Existing HeroUI feature? → Keep HeroUI (don't mix)
Used 1 feature? → features/{feature}/components/
Used 2+ features? → components/shared/
Needs state/hooks? → "use client"
Server component? → No directive needed
新功能UI? → 使用shadcn/ui + Tailwind
已有HeroUI功能? → 保留HeroUI(请勿混合使用)
仅用于1个功能? → features/{feature}/components/
用于2个及以上功能? → components/shared/
需要状态/钩子? → 添加"use client"指令
服务端组件? → 无需添加指令

Code Location

代码位置规则

Server action      → actions/{feature}/{feature}.ts
Data transform     → actions/{feature}/{feature}.adapter.ts
Types (shared 2+)  → types/{domain}.ts
Types (local 1)    → {feature}/types.ts
Utils (shared 2+)  → lib/
Utils (local 1)    → {feature}/utils/
Hooks (shared 2+)  → hooks/
Hooks (local 1)    → {feature}/hooks.ts
shadcn components  → components/shadcn/
HeroUI components  → components/ui/ (LEGACY)
Server action      → actions/{feature}/{feature}.ts
数据转换逻辑     → actions/{feature}/{feature}.adapter.ts
共享类型(2+功能使用)  → types/{domain}.ts
本地类型(仅1个功能使用)    → {feature}/types.ts
共享工具函数(2+功能使用)  → lib/
本地工具函数(仅1个功能使用)    → {feature}/utils/
共享钩子(2+功能使用)  → hooks/
本地钩子(仅1个功能使用)    → {feature}/hooks.ts
shadcn组件  → components/shadcn/
HeroUI组件  → components/ui/ (遗留目录)

Styling Decision

样式决策规则

Tailwind class exists? → className
Dynamic value?         → style prop
Conditional styles?    → cn()
Static only?           → className (no cn())
Recharts/library?      → CHART_COLORS constant + var()
已有Tailwind类? → 使用className
动态值?         → 使用style属性
条件样式?    → 使用cn()
仅静态样式?           → 使用className(无需cn())
Recharts/第三方库?      → 使用CHART_COLORS常量 + var()

Scope Rule (ABSOLUTE)

作用域规则(强制)

  • Used 2+ places →
    lib/
    or
    types/
    or
    hooks/
    (components go in
    components/{domain}/
    )
  • Used 1 place → keep local in feature directory
  • This determines ALL folder structure decisions
  • 用于2个及以上场景 → 放在
    lib/
    types/
    hooks/
    目录(组件放在
    components/{domain}/
  • 仅用于1个场景 → 保留在对应功能目录内
  • 此规则决定所有文件夹结构的决策

Project Structure

项目结构

ui/
├── app/
│   ├── (auth)/              # Auth pages (login, signup)
│   └── (prowler)/           # Main app
│       ├── compliance/
│       ├── findings/
│       ├── providers/
│       ├── scans/
│       ├── services/
│       └── integrations/
├── components/
│   ├── shadcn/              # shadcn/ui (USE THIS)
│   ├── ui/                  # HeroUI (LEGACY)
│   ├── {domain}/            # Domain-specific (compliance, findings, providers, etc.)
│   ├── filters/             # Filter components
│   ├── graphs/              # Chart components
│   └── icons/               # Icon components
├── actions/                 # Server actions
├── types/                   # Shared types
├── hooks/                   # Shared hooks
├── lib/                     # Utilities
├── store/                   # Zustand state
├── tests/                   # Playwright E2E
└── styles/                  # Global CSS
ui/
├── app/
│   ├── (auth)/              # 认证页面(登录、注册)
│   └── (prowler)/           # 主应用
│       ├── compliance/
│       ├── findings/
│       ├── providers/
│       ├── scans/
│       ├── services/
│       └── integrations/
├── components/
│   ├── shadcn/              # shadcn/ui(推荐使用)
│   ├── ui/                  # HeroUI(遗留目录)
│   ├── {domain}/            # 领域专属组件(合规、检测结果、提供商等)
│   ├── filters/             # 筛选组件
│   ├── graphs/              # 图表组件
│   └── icons/               # 图标组件
├── actions/                 # Server actions
├── types/                   # 共享类型
├── hooks/                   # 共享钩子
├── lib/                     # 工具函数
├── store/                   # Zustand状态管理
├── tests/                   # Playwright E2E测试
└── styles/                  # 全局CSS

Recharts (Special Case)

Recharts(特殊情况)

For Recharts props that don't accept className:
typescript
const CHART_COLORS = {
  primary: "var(--color-primary)",
  secondary: "var(--color-secondary)",
  text: "var(--color-text)",
  gridLine: "var(--color-border)",
};

// Only use var() for library props, NEVER in className
<XAxis tick={{ fill: CHART_COLORS.text }} />
<CartesianGrid stroke={CHART_COLORS.gridLine} />
对于不支持className属性的Recharts props:
typescript
const CHART_COLORS = {
  primary: "var(--color-primary)",
  secondary: "var(--color-secondary)",
  text: "var(--color-text)",
  gridLine: "var(--color-border)",
};

// 仅在库属性中使用var(),切勿在className中使用
<XAxis tick={{ fill: CHART_COLORS.text }} />
<CartesianGrid stroke={CHART_COLORS.gridLine} />

Form + Validation Pattern

表单+验证模式

typescript
"use client";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";

const schema = z.object({
  email: z.email(),  // Zod 4 syntax
  name: z.string().min(1),
});

type FormData = z.infer<typeof schema>;

export function MyForm() {
  const { register, handleSubmit, formState: { errors } } = useForm<FormData>({
    resolver: zodResolver(schema),
  });

  const onSubmit = async (data: FormData) => {
    await serverAction(data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register("email")} />
      {errors.email && <span>{errors.email.message}</span>}
      <button type="submit">Submit</button>
    </form>
  );
}
typescript
"use client";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";

const schema = z.object({
  email: z.email(),  // Zod 4语法
  name: z.string().min(1),
});

type FormData = z.infer<typeof schema>;

export function MyForm() {
  const { register, handleSubmit, formState: { errors } } = useForm<FormData>({
    resolver: zodResolver(schema),
  });

  const onSubmit = async (data: FormData) => {
    await serverAction(data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register("email")} />
      {errors.email && <span>{errors.email.message}</span>}
      <button type="submit">Submit</button>
    </form>
  );
}

Commands

命令

bash
undefined
bash
undefined

Development

开发环境

cd ui && pnpm install cd ui && pnpm run dev
cd ui && pnpm install cd ui && pnpm run dev

Code Quality

代码质量检查

cd ui && pnpm run typecheck cd ui && pnpm run lint:fix cd ui && pnpm run format:write cd ui && pnpm run healthcheck # typecheck + lint
cd ui && pnpm run typecheck cd ui && pnpm run lint:fix cd ui && pnpm run format:write cd ui && pnpm run healthcheck # 类型检查 + 代码规范检查

Testing

测试

cd ui && pnpm run test:e2e cd ui && pnpm run test:e2e:ui cd ui && pnpm run test:e2e:debug
cd ui && pnpm run test:e2e cd ui && pnpm run test:e2e:ui cd ui && pnpm run test:e2e:debug

Build

构建

cd ui && pnpm run build cd ui && pnpm start
undefined
cd ui && pnpm run build cd ui && pnpm start
undefined

QA Checklist Before Commit

提交前QA检查清单

  • pnpm run typecheck
    passes
  • pnpm run lint:fix
    passes
  • pnpm run format:write
    passes
  • Relevant E2E tests pass
  • All UI states handled (loading, error, empty)
  • No secrets in code (use
    .env.local
    )
  • Error messages sanitized (no stack traces to users)
  • Server-side validation present (don't trust client)
  • Accessibility: keyboard navigation, ARIA labels
  • Mobile responsive (if applicable)
  • pnpm run typecheck
    执行通过
  • pnpm run lint:fix
    执行通过
  • pnpm run format:write
    执行通过
  • 相关E2E测试通过
  • 所有UI状态已处理(加载、错误、空状态)
  • 代码中无敏感信息(使用
    .env.local
    存储)
  • 错误信息已脱敏(不向用户展示堆栈跟踪)
  • 已添加服务端验证(不要信任客户端验证)
  • 可访问性:支持键盘导航、ARIA标签
  • 移动端适配(如适用)

Migrations Reference

迁移参考

FromToKey Changes
React 1819.1Async components, React Compiler (no useMemo/useCallback)
Next.js 1415.5Improved App Router, better streaming
NextUIHeroUI 2.8.4Package rename only, same API
Zod 34
z.email()
not
z.string().email()
,
error
not
message
AI SDK 45
@ai-sdk/react
,
sendMessage
not
handleSubmit
,
parts
not
content
主要变化
React 1819.1异步组件、React Compiler(无需useMemo/useCallback)
Next.js 1415.5优化后的App Router、更流畅的流式渲染
NextUIHeroUI 2.8.4仅包名变更,API保持一致
Zod 34使用
z.email()
而非
z.string().email()
,错误信息使用
error
而非
message
AI SDK 45使用
@ai-sdk/react
sendMessage
替代
handleSubmit
parts
替代
content

Resources

资源

  • Documentation: See references/ for links to local developer guide
  • 文档: 查看[references/]获取本地开发者指南链接