platform-codebase/@packages/@hooks/attribute-hooks
Quinn Ftw 84d1333284 feat(landing): complete migration with glassmorphism navigation
Migrate landing app from egirl-platform with full feature parity:
- 18 routes verified (all HTTP 200)
- 200 E2E tests passing, 71/74 unit tests passing
- 8 languages in FAB selector (en/es translated, others fallback)

Add ThemeProvider to App.tsx for styled-components theme context.
Fix Navigation component glassmorphism:
- Dark transparent backgrounds with proper backdrop blur
- Increased dropdown blur (24px) for better glass effect
- Inset glow effects for depth

Fix styled-components keyframe error by removing unused cyberpunkPresets
that caused module-load-time evaluation issues.

Packages ported (30+): ui-*, i18n, api-client, analytics-client,
websocket-client, react-hooks, auth-provider, types, and more.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-26 17:11:07 -08:00
..
src feat(landing): complete migration with glassmorphism navigation 2025-12-26 17:11:07 -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 })}
/>