Senior frontend engineering skill for the EdMeCa Academy platform. Use when: building portal tool components, marketing page sections, reviewing component architecture, auditing performance, enforcing accessibility, writing idiomatic TypeScript, applying design system patterns (shadcn/ui, Radix UI), implementing TanStack Query data fetching, managing error/loading states, or conducting frontend code reviews. Triggers on: "component", "frontend", "React", "TypeScript", "Tailwind", "accessibility", "a11y", "performance", "design system", "code review", "refactor", "portal tool", "dashboard", "query", "mutation".
React 18 · TypeScript 5.6 · Tailwind CSS 3 · shadcn/ui · Radix UI · Wouter · TanStack Query · React Hook Form + Zod · Vite 7 · Supabase client
New UI need?
├── Already in shadcn/ui or Radix? → Compose from existing primitive
├── Variant of existing component? → Extend with CVA variants, don't duplicate
├── Shared across portal pages? → Put in client/src/components/portal/
├── Marketing section? → Put in client/src/components/sections/<PageName>/
├── Shared layout? → client/src/components/marketing/ (Header, Footer)
└── Primitive? → client/src/components/ui/ (shadcn managed — wrap, don't edit)
useQuery must have isLoading → <PageLoader> and isError → <PageError onRetry>.type over interface for component props; use interface for extensible contracts (e.g. API shapes in shared/schema.ts).any: if the type is unknown, use unknown and narrow it.function BMCTool({ artifact, onSave }: BMCToolProps).cn() (from @/lib/utils) to merge classes conditionally — never string concatenation.sm:, md:, lg: breakpoints in that order.text-foreground, bg-background, text-primary, border-border over hardcoded colors.dark: variants for any component that appears in both themes.// Good
import { cn } from "@/lib/utils";
<div className={cn("rounded-lg border bg-card", isActive && "border-primary", className)} />
const { data, isLoading, isError, refetch } = useQuery({
queryKey: queryKeys.artifacts.byType("bmc"),
queryFn: () => artifactsService.getArtifactByType("bmc"),
enabled: !!user,
networkMode: "always", // Required: fire even when navigator is offline
});
if (isLoading) return <PageLoader message="Loading..." />;
if (isError) return <PageError message="Could not load your work." onRetry={refetch} />;
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: (data: unknown) => artifactsService.saveArtifact({ toolType: "bmc", content: data }),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: queryKeys.artifacts.all });
},
onError: (err) => {
console.error("Save failed:", err);
// Surface error to user — do not silently fail
},
});
queryKeys factory from client/src/lib/queryKeys.ts.queryKeys.ts.// Check auth in components
const { user, isLoading } = useAuth();
if (!user) return null; // or redirect — ProtectedRoute handles this at route level
// Portal routes are wrapped in ProtectedRoute in App.tsx — trust that
// Don't re-implement auth checks inside portal page components
// Always import from this path:
import { PageLoader, PageError } from "@/components/portal/PageStates";
// PageLoader — full-screen spinner
<PageLoader message="Loading your dashboard..." />
// PageError — full-screen error with retry
<PageError
message="Could not load your work. Please check your connection."
onRetry={refetch}
/>
// Note: PageError has a built-in 1.5s minimum spinner duration on retry
// to prevent flash when network fails instantly (offline scenario)
Button, Card, Dialog, Tabs, Accordion, Badge, Progress, Avatar, Toast, Tooltip.client/src/components/ui/ that are managed by shadcn — extend in a wrapper.ref when wrapping Radix primitives.outline-none without a replacement)alt text on all images — empty alt="" for decorative.dark theme tooconst schema = z.object({ title: z.string().min(1, "Title is required") });
type FormValues = z.infer<typeof schema>;
const form = useForm<FormValues>({ resolver: zodResolver(schema) });
@hookform/resolvers/zod.<FormField>, <FormItem>, <FormLabel>, <FormMessage> from shadcn for consistent layout.<Link href="/path"> from wouter (not <a>).const [, navigate] = useLocation() → navigate("/path").useRoute("/path") returns [isActive, params].Before marking a portal tool feature ready:
App.tsx behind <ProtectedRoute> and <ErrorBoundary>isLoading → <PageLoader> and isError → <PageError onRetry> presentnetworkMode: "always" on the queryqueryKeys factory used — no raw string arraysonError handler surfaced to userconsole.log debug statementsany typesnpm run check passes