Awesome-omni-skill pricing-app-frontend
React frontend patterns for Pricing App - Zustand, CSS Modules, Tesla Design System, permissions
install
source · Clone the upstream repo
git clone https://github.com/diegosouzapw/awesome-omni-skill
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/diegosouzapw/awesome-omni-skill "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/frontend/pricing-app-frontend" ~/.claude/skills/diegosouzapw-awesome-omni-skill-pricing-app-frontend && rm -rf "$T"
manifest:
skills/frontend/pricing-app-frontend/SKILL.mdsource content
Pricing App Frontend - React + Zustand + CSS Modules
CRITICAL RULES - NON-NEGOTIABLE
React Imports
- ALWAYS:
import { useState, useEffect } from 'react' - NEVER:
orimport React from 'react'import * as React
Components
- ALWAYS: Functional components with hooks
- ALWAYS: Named imports for hooks
- ALWAYS: Prop destructuring:
function Button({ label, onClick }) - NEVER: Class components
- NEVER: Default export for multiple components in same file
State Management
- ALWAYS: Zustand for global state (auth)
- ALWAYS: React Context for theme, permissions
- ALWAYS: Local state for component-specific data
- NEVER: Lift state unnecessarily
Styling
- ALWAYS: CSS Modules for component styles:
import styles from './Component.module.css' - ALWAYS: Design tokens from
:design-tokens.css
,var(--bg-primary)var(--text-primary) - ALWAYS: Tesla components when available (
,buttons-tesla.css
,modals-tesla.css
)table-tesla.css - NEVER: Inline styles (except dynamic values)
- NEVER: Hardcoded colors (use design tokens)
- NEVER: Tailwind utilities (project uses CSS Modules)
API Calls
- ALWAYS: Use axios from
services/api.js - ALWAYS: Check token before API calls:
localStorage.getItem('token') - ALWAYS: Handle loading states
- ALWAYS: Show user feedback on errors
- NEVER: Fetch without error handling
Accessibility
- ALWAYS: Alt text on images:
<img src="logo.png" alt="Company logo" /> - ALWAYS: Semantic HTML:
not<button><div onClick> - ALWAYS: ARIA labels for icon-only buttons:
<button aria-label="Close modal">
PROJECT STRUCTURE
frontend/src/ ├── pages/ # Full page components │ ├── Productos.jsx │ ├── Ventas.jsx │ └── Admin.jsx ├── components/ # Reusable components │ ├── ModalTesla.jsx │ ├── PricingModal.jsx │ ├── Navbar.jsx │ └── turbo/ # Domain-specific components ├── contexts/ # React contexts │ ├── ThemeContext.jsx # Dark mode │ └── PermisosContext.jsx # User permissions ├── hooks/ # Custom hooks │ ├── useDebounce.js │ ├── usePermisos.js │ └── useServerPagination.js ├── store/ # Zustand stores │ └── authStore.js # Auth state ├── services/ # API client │ └── api.js # Axios instance └── styles/ # Global CSS, design tokens ├── design-tokens.css ├── buttons-tesla.css ├── modals-tesla.css └── table-tesla.css
PATTERNS
Functional Component with Hooks
import { useState, useEffect } from 'react'; import styles from './ProductosList.module.css'; export default function ProductosList({ onSelect }) { const [productos, setProductos] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); useEffect(() => { fetchProductos(); }, []); const fetchProductos = async () => { setLoading(true); setError(null); try { const response = await fetch('/api/productos'); const data = await response.json(); setProductos(data); } catch (err) { setError('Error al cargar productos'); console.error(err); } finally { setLoading(false); } }; if (loading) return <div className={styles.loading}>Cargando...</div>; if (error) return <div className={styles.error}>{error}</div>; return ( <div className={styles.container}> {productos.map(p => ( <div key={p.id} onClick={() => onSelect(p)}> {p.descripcion} </div> ))} </div> ); }
Using Zustand Store
import { create } from 'zustand'; export const useAuthStore = create((set) => ({ user: null, token: localStorage.getItem('token'), setUser: (user) => set({ user }), setToken: (token) => { localStorage.setItem('token', token); set({ token }); }, logout: () => { localStorage.removeItem('token'); set({ user: null, token: null }); } })); // Usage in component import { useAuthStore } from '@/store/authStore'; function Navbar() { const { user, logout } = useAuthStore(); return ( <nav> <span>{user?.nombre}</span> <button onClick={logout}>Salir</button> </nav> ); }
Using Context (Permissions)
import { createContext, useContext, useState, useEffect } from 'react'; import { useAuthStore } from '@/store/authStore'; const PermisosContext = createContext(); export function PermisosProvider({ children }) { const { user } = useAuthStore(); const [permisos, setPermisos] = useState([]); useEffect(() => { if (user?.roles) { // Calculate permissions from roles const allPermisos = user.roles.flatMap(role => PERMISOS_CATEGORIAS[role] || [] ); setPermisos([...new Set(allPermisos)]); } }, [user]); const tienePermiso = (categoria) => permisos.includes(categoria); return ( <PermisosContext.Provider value={{ permisos, tienePermiso }}> {children} </PermisosContext.Provider> ); } export const usePermisos = () => useContext(PermisosContext); // Usage in component function AdminPanel() { const { tienePermiso } = usePermisos(); if (!tienePermiso('config')) { return <div>No tienes permiso</div>; } return <div>Panel de Admin</div>; }
CSS Modules with Design Tokens
/* ProductosList.module.css */ .container { background: var(--bg-primary); color: var(--text-primary); padding: var(--spacing-md); border-radius: var(--radius-md); box-shadow: var(--shadow-sm); } .loading { color: var(--text-secondary); text-align: center; padding: var(--spacing-lg); } .error { background: var(--error-bg); color: var(--error-text); padding: var(--spacing-sm); border-radius: var(--radius-sm); } /* Dark mode support (automatic via design tokens) */
Tesla Button Component
import styles from './Button.module.css'; export default function Button({ label, onClick, variant = 'primary', disabled = false }) { return ( <button className={`${styles.btnBase} ${styles[`btn${variant.charAt(0).toUpperCase() + variant.slice(1)}`]}`} onClick={onClick} disabled={disabled} > {label} </button> ); }
/* Button.module.css */ .btnBase { composes: btn-base from '../../styles/buttons-tesla.css'; } .btnPrimary { composes: btn-primary from '../../styles/buttons-tesla.css'; } .btnSecondary { composes: btn-secondary from '../../styles/buttons-tesla.css'; }
Custom Hook
// hooks/useDebounce.js import { useState, useEffect } from 'react'; export function useDebounce(value, delay = 500) { const [debouncedValue, setDebouncedValue] = useState(value); useEffect(() => { const handler = setTimeout(() => { setDebouncedValue(value); }, delay); return () => { clearTimeout(handler); }; }, [value, delay]); return debouncedValue; } // Usage function SearchBar() { const [query, setQuery] = useState(''); const debouncedQuery = useDebounce(query, 300); useEffect(() => { if (debouncedQuery) { fetchResults(debouncedQuery); } }, [debouncedQuery]); return <input value={query} onChange={e => setQuery(e.target.value)} />; }
NAMING CONVENTIONS
| Entity | Pattern | Example |
|---|---|---|
| Component file | | |
| Hook file | | |
| Utility file | | |
| CSS Module | | |
| CSS class | | , |
COMMON PITFALLS
Frontend
- ❌ Don't use
without dependencies array → Will cause infinite loopsuseEffect - ❌ Don't mutate state directly → Use setState functions
- ❌ Don't forget to cleanup effects → Clear timers, unsubscribe
- ❌ Don't store sensitive data in localStorage → Only JWT token
- ❌ Don't use inline styles → Use CSS Modules with design tokens
- ❌ Don't hardcode colors → Use
var(--color-name)
COMMANDS
# Development cd frontend npm install npm run dev # Build npm run build npm run preview # Linting npm run lint
CLOUDFLARE DESIGN SYSTEM
Sidebar Navigation (Collapsible)
Components:
: Main sidebar with 3 states (expanded/collapsed/hover-peek)Sidebar.jsx
: Collapsible section with menu itemsSidebarSection.jsx
: Minimal top headerTopBar.jsx
: Layout wrapperAppLayout.jsx
States:
- Expanded (pinned): 240px width, shows icons + text
- Collapsed (pinned): 64px width, shows only icons
- Hover-peek (temporary): Expands to 240px on hover when collapsed
Pattern:
import Sidebar from '@/components/Sidebar'; import TopBar from '@/components/TopBar'; import AppLayout from '@/components/AppLayout'; // En App.jsx o router <AppLayout> <Outlet /> </AppLayout>
Adding menu items: Edit
Sidebar.jsx and add to menuSections array:
{ id: 'productos', title: 'Productos', icon: '📦', defaultOpen: true, items: [ { label: 'Productos', path: '/productos', permiso: 'productos.ver' }, // ... ], }
CloudflareCard Component
Variants:
: Standard padding (20px)default
: Small padding (16px)compact
: For dashboard metricsmetric
Pattern:
import CloudflareCard, { MetricCard } from '@/components/CloudflareCard'; // Standard card <CloudflareCard title="Dominios" action={<button>+ Agregar</button>} > <p>Content here</p> </CloudflareCard> // Metric card for dashboards <MetricCard label="Solicitudes HTTP" value="138,3k" trend="+3.65%" trendDirection="up" chart={<Sparkline data={[...]} />} info="Información adicional" />
Styling:
- Use
,var(--cf-card-bg)var(--cf-card-border) - Compact by default (no grotesco padding)
- Subtle shadows:
var(--cf-card-shadow)
AlertBanner Component
Variants:
: Blue background (default)info
: Orange backgroundwarning
: Green backgroundsuccess
: Red backgrounderror
Pattern:
import AlertBanner, { AlertBannerContainer } from '@/components/AlertBanner'; <AlertBannerContainer> <AlertBanner id="unique-banner-id" variant="info" message="Mensaje informativo aquí" action={{ label: 'Ver más', onClick: () => navigate('/page') }} dismissible={true} /> </AlertBannerContainer>
Features:
- Persists dismiss state in localStorage
- Optional action button
- Can be non-dismissible with
dismissible={false} - Use
to ignore localStoragepersistent={true}
NOTE: AlertBanners will be managed from backend admin panel (dynamic system TBD).
Design Tokens - Cloudflare
Available tokens:
/* Backgrounds */ --cf-bg-app: #000000 --cf-bg-sidebar: #0a0a0a --cf-bg-card: #181818 --cf-bg-hover: #1f1f1f /* Borders */ --cf-border-subtle: #1a1a1a --cf-border-default: #2a2a2a /* Text */ --cf-text-primary: #ffffff --cf-text-secondary: rgba(255, 255, 255, 0.7) --cf-text-tertiary: rgba(255, 255, 255, 0.5) /* Accent */ --cf-accent-blue: #3b82f6 --cf-accent-blue-hover: #60a5fa /* Layout */ --cf-sidebar-width-expanded: 240px --cf-sidebar-width-collapsed: 64px --cf-topbar-height: 56px /* Cards */ --cf-card-padding-compact: 16px --cf-card-padding-default: 20px --cf-card-radius: 8px
QA CHECKLIST
- Components use functional syntax with hooks
- No
statementsimport React - Proper error handling on API calls
- Loading states shown to user
- CSS Modules used (no inline styles)
- Design tokens used (no hardcoded colors)
- Dark mode works (test ThemeContext)
- Permissions checked where needed
- Alt text on images
- Semantic HTML used
- Cloudflare components use correct tokens (--cf-*)
REFERENCES
External
- React docs: https://react.dev
- Zustand docs: https://zustand-demo.pmnd.rs
Internal
- Frontend References - Links to all internal docs
- Design tokens: design-tokens.css
- Cloudflare Sidebar: Sidebar.jsx
- Cloudflare Cards: CloudflareCard.jsx
- Alert Banners: AlertBanner.jsx
- Tesla buttons: buttons-tesla.css
- Tesla modals: modals-tesla.css
- Tesla tables: table-tesla.css
- ThemeContext: ThemeContext.jsx
- PermisosContext: PermisosContext.jsx