.claude/skills/glapi-frontend-guidelines/SKILL.md
Frontend development guidelines for Adteco's React 18/19 + ShadCN/Radix UI + Tailwind stack. Patterns for building accessible, performant components with type safety, TanStack Query data fetching, and modern styling.
npx skillsauth add adteco/glapi adteco-frontend-guidelinesInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Comprehensive guide for building frontend components in Adteco using:
Creating a component? Follow this checklist:
@/components/uicn() utility for conditional/merged classes (from @/lib/utils)lucide-react or @heroicons/reactcva for component variants if neededuseQuery, useMutation)Creating a feature? Set up this structure:
apps/web/src/components/, hooks/, types/types/index.ts@/components/uiimport { Button } from '@/components/ui/button';
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
DialogFooter,
} from '@/components/ui/dialog';
import {
Select,
SelectTrigger,
SelectContent,
SelectItem,
} from '@/components/ui/select';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import {
Form,
FormField,
FormItem,
FormLabel,
FormControl,
FormMessage,
} from '@/components/ui/form';
import { cn } from '@/lib/utils'; // Class utility
import { Plus, Trash2, Settings, Check } from 'lucide-react';
// OR
import { PlusIcon, TrashIcon, Cog6ToothIcon } from '@heroicons/react/24/outline';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
// In component
const { data, isLoading, error } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetch(`/api/users/${userId}`).then(res => res.json()),
});
const mutation = useMutation({
mutationFn: (data) => fetch('/api/users', {
method: 'POST',
body: JSON.stringify(data),
}),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['users'] });
},
});
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { useTheme } from 'next-themes';
import { useUser, useAuth, SignInButton, UserButton } from '@clerk/nextjs';
const { user, isLoaded, isSignedIn } = useUser();
const { getToken, signOut } = useAuth();
// React
import React, { useState, useCallback, useMemo } from 'react';
// UI Components (ShadCN)
import { Button } from '@/components/ui/button';
import { Dialog, DialogContent } from '@/components/ui/dialog';
import { Input } from '@/components/ui/input';
import { Select } from '@/components/ui/select';
import { cn } from '@/lib/utils';
// Icons
import { Plus, Edit, Trash2 } from 'lucide-react';
// TanStack Query
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
// Forms
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
// Dark Mode
import { useTheme } from 'next-themes';
// Clerk Auth
import { useUser, useAuth } from '@clerk/nextjs';
// Types
import type { User } from '@/types';
Modern Adteco components use:
@/components/uicn() for conditional class merging (from @/lib/utils)cva for variant-based stylingKey Concepts:
apps/web/src/components/ui/📖 Complete Guide: resources/component-patterns.md
PRIMARY APPROACH: Utility Classes
cn() for conditional/merged classessm:, md:, lg: prefixesComponent Variants:
class-variance-authority (cva) for variantsbuttonVariants, cardVariants, etc.VariantProps📖 Complete Guide: resources/tailwind-styling.md
ShadCN Philosophy:
Radix UI Philosophy:
Available Components:
📖 Complete Guide: resources/radix-ui-integration.md
PRIMARY PATTERN: TanStack Query
Query Pattern:
const { data, isLoading, error } = useQuery({
queryKey: ['users', userId],
queryFn: async () => {
const res = await fetch(`/api/users/${userId}`);
if (!res.ok) throw new Error('Failed to fetch');
return res.json();
},
staleTime: 5 * 60 * 1000, // 5 minutes
});
Mutation Pattern:
const queryClient = useQueryClient();
const updateUser = useMutation({
mutationFn: async (data) => {
const res = await fetch(`/api/users/${userId}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
if (!res.ok) throw new Error('Failed to update');
return res.json();
},
onSuccess: () => {
// Invalidate and refetch
queryClient.invalidateQueries({ queryKey: ['users', userId] });
},
});
📖 Complete Guide: resources/data-fetching.md
Typical Structure:
apps/web/src/
app/ # Next.js app router pages
components/
ui/ # ShadCN components
shared/ # Shared components
lib/
utils.ts # cn() utility and helpers
hooks/ # Custom React hooks
types/ # TypeScript types
Component Files:
.tsx extension📖 Complete Guide: resources/file-organization.md
Adteco uses next-themes:
class="dark")Pattern:
import { useTheme } from 'next-themes';
const { theme, setTheme } = useTheme();
<button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>
Toggle Theme
</button>
Styling:
<div className="bg-background text-foreground">
Content adapts to theme automatically
</div>
Clerk Patterns:
useUser() for current user datauseAuth() for auth state and tokensExample:
import { useUser } from '@clerk/nextjs';
export function MyComponent() {
const { user, isLoaded, isSignedIn } = useUser();
if (!isLoaded) return <div>Loading...</div>;
if (!isSignedIn) return <div>Not signed in</div>;
return <div>Hello {user.firstName}!</div>;
}
Optimization Patterns:
useMemo: Expensive computationsuseCallback: Event handlers passed to childrenReact.memo: Expensive componentsnext/image📖 Complete Guide: resources/performance.md
Standards:
import type { User } from '@/types'any type📖 Complete Guide: resources/typescript-standards.md
@/components/ui directorycn() from @/lib/utilsbg-primary, text-foreground, etc.'use client'; // If using client-side features
import * as React from 'react';
import { Button } from '@/components/ui/button';
import { cn } from '@/lib/utils';
import { Plus } from 'lucide-react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
interface MyComponentProps {
userId: string;
onSuccess?: () => void;
className?: string;
}
export function MyComponent({
userId,
onSuccess,
className,
}: MyComponentProps) {
const [isActive, setIsActive] = React.useState(false);
const queryClient = useQueryClient();
// TanStack Query - fetch data
const { data: user } = useQuery({
queryKey: ['user', userId],
queryFn: async () => {
const res = await fetch(`/api/users/${userId}`);
if (!res.ok) throw new Error('Failed to fetch user');
return res.json();
},
});
// TanStack Query - mutation
const updateUser = useMutation({
mutationFn: async (data: { active: boolean }) => {
const res = await fetch(`/api/users/${userId}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
if (!res.ok) throw new Error('Failed to update');
return res.json();
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['user', userId] });
onSuccess?.();
},
});
const handleClick = React.useCallback(() => {
const newActive = !isActive;
setIsActive(newActive);
updateUser.mutate({ active: newActive });
}, [isActive, updateUser]);
return (
<div className={cn('flex flex-col gap-4 p-4 bg-background', className)}>
<h2 className="text-2xl font-bold text-foreground">
{user?.name}
</h2>
<Button
onClick={handleClick}
className={cn(
'flex items-center gap-2',
isActive && 'bg-green-600 hover:bg-green-700'
)}
>
<Plus className="w-4 h-4" />
{isActive ? 'Active' : 'Inactive'}
</Button>
</div>
);
}
export default MyComponent;
// Flexbox
<div className="flex flex-col gap-4"> // Vertical stack with gap
<div className="flex items-center justify-between"> // Horizontal with space-between
// Grid
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
// Padding & Margin (0.25rem units)
className="p-4 px-6 py-2 m-4 mx-auto"
// Gap
className="gap-2 gap-x-4 gap-y-2"
// Background
className="bg-background bg-primary bg-secondary bg-destructive"
// Text
className="text-foreground text-primary text-muted-foreground"
// Border
className="border border-border border-primary"
// Opacity
className="bg-primary/90 text-foreground/70"
className="text-sm md:text-base lg:text-lg"
className="hidden md:block"
className="w-full md:w-1/2 lg:w-1/3"
// These adapt automatically based on :root and .dark in globals.css
className="bg-background text-foreground" // White bg in light, dark bg in dark mode
className="bg-card text-card-foreground" // Card colors adapt
Don't use MUI or other component libraries - Use ShadCN/Radix UI
// ❌ Wrong
import { Button } from '@mui/material';
// ✅ Correct
import { Button } from '@/components/ui/button';
Don't use inline styles - Use Tailwind classes
// ❌ Wrong
<div style={{ padding: '16px', backgroundColor: '#0066FF' }}>
// ✅ Correct
<div className="p-4 bg-primary">
Don't manually merge classes - Use cn() utility
// ❌ Wrong
className={`base-class ${condition ? 'conditional' : ''} ${className}`}
// ✅ Correct
className={cn("base-class", condition && "conditional", className)}
Don't use raw fetch without TanStack Query - Use useQuery/useMutation
// ❌ Wrong
const [data, setData] = useState(null);
useEffect(() => {
fetch('/api/users/123').then(res => res.json()).then(setData);
}, []);
// ✅ Correct
const { data } = useQuery({
queryKey: ['user', '123'],
queryFn: () => fetch('/api/users/123').then(res => res.json()),
});
Don't create custom theme context - Use next-themes
// ❌ Wrong
const [theme, setTheme] = useState('light');
// ✅ Correct
import { useTheme } from 'next-themes';
const { theme, setTheme } = useTheme();
Don't skip TypeScript types - Always type props
// ❌ Wrong
export function MyComponent(props) {
// ✅ Correct
interface MyComponentProps {
userId: string;
onSuccess?: () => void;
}
export function MyComponent({ userId, onSuccess }: MyComponentProps) {
@/components/ui@/lib/utilsbg-primary, etc.)| Need to... | Read this resource | |------------|-------------------| | Create a component | component-patterns.md | | Style with Tailwind | tailwind-styling.md | | Use ShadCN/Radix UI | radix-ui-integration.md | | Fetch data | data-fetching.md | | Build forms | form-patterns.md | | Organize files | file-organization.md | | Optimize performance | performance.md | | TypeScript patterns | typescript-standards.md |
Skill Status: Production-ready for Adteco development
tools
Create and manage Claude Code skills following Anthropic best practices. Use when creating new skills, modifying skill-rules.json, understanding trigger patterns, working with hooks, debugging skill activation, or implementing progressive disclosure. Covers skill structure, YAML frontmatter, trigger types (keywords, intent patterns, file paths, content patterns), enforcement levels (block, suggest, warn), hook mechanisms (UserPromptSubmit, PreToolUse), session tracking, and the 500-line rule.
development
# Pre-Deployment Checklist Skill ## Purpose Run comprehensive quality checks before committing code and creating pull requests. This skill ensures code quality, documentation, API specs, and tests are all in order before deployment. ## When to Use This Skill - Before committing significant changes - Before creating a pull request - When user invokes `/ship`, `/pre-deploy`, or `/checklist` - When preparing code for production deployment ## Checklist Items ### 1. TypeScript Compilation **Com
development
Row Level Security (RLS) implementation guide for GLAPI multi-tenant database isolation. Covers PostgreSQL RLS policies, session variables, contextual database connections, tRPC middleware, and common troubleshooting. Use when working with RLS, multi-tenancy, organization isolation, database security, or debugging RLS policy violations.
development
GLAPI backend development guide for Next.js + TRPC + Drizzle ORM + PostgreSQL + Clerk. TRPC for internal type-safety, REST API exposure via OpenAPI. Covers layered architecture (routers → services → Drizzle), dual TRPC/REST endpoints, Clerk authentication, and testing strategies.