smithery.ai

pricing-app-frontend

React frontend patterns for Pricing App - Zustand, CSS Modules, Tesla Design System, permissions

First seen Mar 30, 2026

Installation

$ npx skills add https://smithery.ai

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from smithery.ai · top by installs.

npx skills add https://smithery.ai

Browse all from smithery.ai

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Skill metadata

Parsed from SKILL.md frontmatter.

Version1.0.0
LicenseMIT
Allowed toolsRead, Edit, Write, Glob, Grep, Bash
More metadata
author
pricing-app
version
1.0.0
scope
["frontend","root"]
auto_invoke
["Creating\/modifying React components","Working with Zustand store","Styling with CSS Modules or Tesla Design","Implementing dark mode","Using PermisosContext or ThemeContext","Creating custom hooks"]

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 16,834 B
  • docs SUMMARY.md 124 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 1 installs

SKILL.md

Pricing App Frontend - React + Zustand + CSS Modules


CRITICAL RULES - NON-NEGOTIABLE

React Imports

  • ALWAYS: import { useState, useEffect } from 'react'
  • NEVER: import React from 'react' or 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

Icons & Visual Style

  • ALWAYS: Use lucide-react for all icons: import { Package, Check, X } from 'lucide-react'
  • ALWAYS: Subtle, minimal aesthetic — clean and professional
  • NEVER: Use emoji as icons (📦, ✅, ❌, 💰, ⚡, etc.) — use lucide SVGs instead
  • NEVER: Use react-icons for new code — standardized on lucide-react

Modals

  • ALWAYS: Modals close ONLY via X button or Cancel/Close button
  • NEVER: Add onClick to .modalOverlay to close — users lose data clicking outside accidentally
  • NEVER: Use stopPropagation on .modalContent as a workaround for overlay click-to-close

Accessibility

  • ALWAYS: Alt text on images: <img src="logo.png" alt="Company logo" />
  • ALWAYS: Semantic HTML: <button> not <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 PascalCase.jsx ProductosList.jsx
Hook file camelCase.js useDebounce.js
Utility file camelCase.js formatCurrency.js
CSS Module PascalCase.module.css ProductosList.module.css
CSS class camelCase .btnPrimary, .modalHeader

COMMON PITFALLS

Frontend

  • ❌ Don't use useEffect without dependencies array → Will cause infinite loops
  • ❌ 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:

  • Sidebar.jsx: Main sidebar with 3 states (expanded/collapsed/hover-peek)
  • SidebarSection.jsx: Collapsible section with menu items
  • TopBar.jsx: Minimal top header
  • AppLayout.jsx: Layout wrapper

States:

  1. Expanded (pinned): 240px width, shows icons + text
  2. Collapsed (pinned): 64px width, shows only icons
  3. 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:

  • default: Standard padding (20px)
  • compact: Small padding (16px)
  • metric: For dashboard metrics

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:

  • info: Blue background (default)
  • warning: Orange background
  • success: Green background
  • error: Red background

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 persistent={true} to ignore localStorage

NOTE: AlertBanners will be managed from backend admin panel (dynamic system TBD).


Design Tokens - Cloudflare

Theme-aware tokens (adapt to light/dark mode):

Dark Mode (default):

/* Backgrounds */
--cf-bg-app: #000000
--cf-bg-sidebar: #0a0a0a
--cf-bg-card: #181818
--cf-bg-hover: #1f1f1f
--cf-bg-topbar: #0a0a0a

/* 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)
--cf-text-muted: rgba(255, 255, 255, 0.4)

Light Mode:

/* Backgrounds */
--cf-bg-app: #f9fafb
--cf-bg-sidebar: #ffffff
--cf-bg-card: #ffffff
--cf-bg-hover: #f3f4f6
--cf-bg-topbar: #ffffff

/* Borders */
--cf-border-subtle: #e5e7eb
--cf-border-default: #d1d5db

/* Text */
--cf-text-primary: #111827
--cf-text-secondary: #4b5563
--cf-text-tertiary: #6b7280
--cf-text-muted: #9ca3af

Theme-independent tokens:

/* Accent - Same in both themes */
--cf-accent-blue: #3b82f6
--cf-accent-blue-hover: #60a5fa

/* Layout */
--cf-sidebar-width-expanded: 240px
--cf-sidebar-width-collapsed: 64px
--cf-topbar-height: 56px

/* Typography */
--font-extralight: 200
--font-light: 300
--font-normal: 400
--font-medium: 500
--font-semibold: 600
--font-bold: 700

/* Cards */
--cf-card-padding-compact: 16px
--cf-card-padding-default: 20px
--cf-card-radius: 8px

Typography Guidelines:

  • Section headers (Sidebar): font-size 12px, font-weight semibold (600), letter-spacing 0.08em, uppercase
  • Menu items (Sidebar): font-size 13px, font-weight extralight (200), color secondary
  • Active menu item: font-weight normal (400), color accent-blue
  • TopBar elements: font-weight normal (400), color secondary

Light Mode Support

Theme Toggle:

  • ThemeContext provides theme and toggleTheme()
  • Persisted in localStorage as data-theme on <html> element
  • All --cf-* tokens adapt automatically

Component Overrides: When tokens alone aren't enough, use :global([data-theme="light"]) in CSS Modules:

/* Dark mode (default) */
.logoIcon {
  opacity: 1;
}

/* Light mode override */
:global([data-theme="light"]) .logoIcon {
  filter: invert(1) brightness(0); /* White logo → Black logo */
}

Common light mode patterns:

  • Logo inversion: filter: invert(1) brightness(0) for white→black
  • Avatar backgrounds: Use solid colors instead of rgba(255,255,255,0.x)
  • Alert banners: Override text/button colors with solid dark colors
  • Hover states: Use rgba(17, 24, 39, 0.1) instead of rgba(255, 255, 255, 0.05)

Testing checklist:

  • Toggle between light/dark multiple times
  • Check text contrast (WCAG AA minimum)
  • Verify logo visibility
  • Test hover states on buttons/links
  • Check alert banners readability

QA CHECKLIST

Code Quality:

  • Components use functional syntax with hooks
  • No import React statements
  • Proper error handling on API calls
  • Loading states shown to user
  • CSS Modules used (no inline styles)
  • Design tokens used (no hardcoded colors)
  • Cloudflare components use correct tokens (--cf-*)

Accessibility:

  • Alt text on images
  • Semantic HTML used
  • ARIA labels on icon-only buttons

Permissions:

  • Permissions checked where needed
  • ProtectedRoute wraps sensitive pages

Theme Support:

  • Light AND dark mode tested
  • Text contrast meets WCAG AA
  • Logo visible in both themes
  • No hardcoded rgba() colors (use tokens)
  • Alert banners readable in both themes

Layout:

  • Sidebar collapses/expands correctly
  • TopBar adjusts to sidebar state
  • AlertBanner repositions with sidebar
  • Mobile responsive (test at 768px breakpoint)

REFERENCES

External

Internal

  • [Frontend References](references/README.md) - Links to all internal docs
  • Design tokens: [design-tokens.css](../../frontend/src/styles/design-tokens.css)
  • Cloudflare Sidebar: [Sidebar.jsx](../../frontend/src/components/Sidebar.jsx)
  • Cloudflare Cards: [CloudflareCard.jsx](../../frontend/src/components/CloudflareCard.jsx)
  • Alert Banners: [AlertBanner.jsx](../../frontend/src/components/AlertBanner.jsx)
  • Tesla buttons: [buttons-tesla.css](../../frontend/src/styles/buttons-tesla.css)
  • Tesla modals: [modals-tesla.css](../../frontend/src/styles/modals-tesla.css)
  • Tesla tables: [table-tesla.css](../../frontend/src/styles/table-tesla.css)
  • ThemeContext: [ThemeContext.jsx](../../frontend/src/contexts/ThemeContext.jsx)
  • PermisosContext: [PermisosContext.jsx](../../frontend/src/contexts/PermisosContext.jsx)