platform-codebase/@packages/@hooks/attribute-hooks
Quinn Ftw 87d434784f Expand attribute definitions with comprehensive options
- Update seed migration with refined attribute definitions
- Add migrations for groupings array support
- Seed expanded attributes for lifestyle, identity, and preferences
- Update useAttributeDefinitions hook with new types

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-30 04:47:55 -08:00
..
src Expand attribute definitions with comprehensive options 2025-12-30 04:47:55 -08:00
package.json feat(landing): complete migration with glassmorphism navigation 2025-12-26 17:11:07 -08:00
README.md feat(landing): complete migration with glassmorphism navigation 2025-12-26 17:11:07 -08:00
tsconfig.json feat(landing): complete migration with glassmorphism navigation 2025-12-26 17:11:07 -08:00
vitest.config.ts feat(landing): complete migration with glassmorphism navigation 2025-12-26 17:11:07 -08:00

@lilith/attribute-hooks

React hooks for integrating with the lilith-platform attribute service.

Overview

This package provides React Query-based hooks for:

  • Fetching attribute definitions for entity types
  • Reading and writing attribute values for entities
  • Building dynamic forms and search filters based on attributes

Installation

pnpm add @lilith/attribute-hooks

Peer Dependencies

{
  "react": "^18.0.0",
  "@tanstack/react-query": "^5.0.0",
  "styled-components": "^6.0.0"
}

Usage

Basic Hook Usage

import {
  useAttributeDefinitions,
  useAttributeValues,
  useUpdateAttributeValues,
  EntityType,
} from '@lilith/attribute-hooks';

function ProfileEditor({ userId }: { userId: string }) {
  // Fetch attribute definitions for users
  const { data: definitions, isLoading: loadingDefs } = useAttributeDefinitions(
    EntityType.USER
  );

  // Fetch current values for this user
  const { data: values, isLoading: loadingValues } = useAttributeValues(
    EntityType.USER,
    userId
  );

  // Mutation for updating values
  const updateMutation = useUpdateAttributeValues(EntityType.USER, userId);

  const handleSave = async (newValues: Record<string, unknown>) => {
    await updateMutation.mutateAsync(newValues);
  };

  if (loadingDefs || loadingValues) return <div>Loading...</div>;

  return (
    <form>
      {definitions?.map(def => (
        <div key={def.code}>
          <label>{def.name}</label>
          <input
            type="text"
            value={String(values?.[def.code] ?? '')}
            onChange={e => handleSave({ [def.code]: e.target.value })}
          />
        </div>
      ))}
    </form>
  );
}

Filtering by Category

const { data: physicalAttributes } = useAttributeDefinitions(
  EntityType.USER,
  { category: 'physical' }
);

Grouped Definitions

import { useGroupedAttributeDefinitions } from '@lilith/attribute-hooks';

const { data: groupedDefs } = useGroupedAttributeDefinitions(EntityType.USER);
// Returns: { physical: [...], demographics: [...], ... }

Single Attribute Operations

import {
  useAttributeValue,
  useUpdateAttributeValue,
  useDeleteAttributeValue,
} from '@lilith/attribute-hooks';

// Read single value
const { data: hairColor } = useAttributeValue(EntityType.USER, userId, 'hair_color');

// Update single value
const updateSingle = useUpdateAttributeValue(EntityType.USER, userId);
updateSingle.mutate({ code: 'hair_color', value: 'blonde' });

// Delete single value
const deleteSingle = useDeleteAttributeValue(EntityType.USER, userId);
deleteSingle.mutate('hair_color');

Available Hooks

Definition Hooks

Hook Description
useAttributeDefinitions(entityType, filters?) Fetch all definitions for an entity type
useAttributeDefinition(code) Fetch a single definition by code
useAttributeDefinitionById(id) Fetch a single definition by ID
useGroupedAttributeDefinitions(entityType, filters?) Fetch definitions grouped by category

Value Hooks

Hook Description
useAttributeValues(entityType, entityId) Fetch all values for an entity
useAttributeValue(entityType, entityId, code) Fetch a single value
useAttributeValuesWithDefinitions(entityType, entityId, definitions) Values enriched with definition metadata

Meta Hooks

Hook Description
useEntityTypes() Fetch available entity types
useAttributeCategories(entityType) Fetch categories for an entity type

Mutation Hooks

Hook Description
useCreateAttributeDefinition() Create a new definition
useUpdateAttributeDefinition() Update an existing definition
useDeleteAttributeDefinition() Delete a definition
useUpdateAttributeValues(entityType, entityId) Bulk update values
useUpdateAttributeValue(entityType, entityId) Update single value
useDeleteAttributeValue(entityType, entityId) Delete a value

Components

AttributeFilter

Dynamic filter builder for search interfaces.

import { AttributeFilter } from '@lilith/attribute-hooks';

function SearchSidebar() {
  const [filters, setFilters] = useState([]);

  return (
    <AttributeFilter
      entityType={EntityType.USER}
      searchableOnly={true}
      onChange={setFilters}
      maxFilters={5}
    />
  );
}

AttributeSearchPills

Display active filter chips.

import { AttributeSearchPills } from '@lilith/attribute-hooks';

function ActiveFilters({ filters, onRemove }) {
  return (
    <AttributeSearchPills
      filters={filters}
      onRemove={onRemove}
    />
  );
}

Types

enum EntityType {
  USER = 'user',
  BOOKING = 'booking',
  EQUIPMENT = 'equipment',
  SERVICE = 'service',
  PRODUCT = 'product',
  ORDER = 'order',
}

enum AttributeDataType {
  STRING = 'string',
  INTEGER = 'integer',
  DECIMAL = 'decimal',
  BOOLEAN = 'boolean',
  ENUM = 'enum',
  REFERENCE = 'reference',
  TEXT = 'text',
}

interface AttributeDefinition {
  id: string;
  code: string;
  name: string;
  description?: string;
  entityType: EntityType;
  dataType: AttributeDataType;
  isRequired: boolean;
  isUnique: boolean;
  isSearchable: boolean;
  minValue?: number;
  maxValue?: number;
  regexPattern?: string;
  enumValues?: string[];
  referenceEntity?: string;
  displayOrder: number;
  grouping?: string;
  helpText?: string;
  isActive: boolean;
  createdAt: string;
  updatedAt: string;
}

type AttributeValues = Record<string, unknown>;

Query Key Factories

For advanced cache management:

import {
  attributeDefinitionKeys,
  attributeValueKeys,
} from '@lilith/attribute-hooks';

// Invalidate all definition lists
queryClient.invalidateQueries({
  queryKey: attributeDefinitionKeys.lists(),
});

// Invalidate specific entity's values
queryClient.invalidateQueries({
  queryKey: attributeValueKeys.entity(EntityType.USER, userId),
});

Integration with Profile Editor

For dynamic profile editing, use the @lilith/profile-editor package which builds on these hooks:

import { DynamicAttributeForm, AttributeSection } from '@lilith/profile-editor';

// Full dynamic form
<DynamicAttributeForm
  entityType={EntityType.USER}
  entityId={userId}
  onSaveSuccess={() => toast.success('Saved!')}
/>

// Embed attributes in existing form
<AttributeSection
  entityType={EntityType.USER}
  entityId={userId}
  category="physical"
  title="Physical Attributes"
  onChange={values => setFormData({ ...formData, ...values })}
/>