platform-codebase/@packages/@hooks/attribute-hooks/README.md
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

285 lines
6.5 KiB
Markdown

# @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
```bash
pnpm add @lilith/attribute-hooks
```
## Peer Dependencies
```json
{
"react": "^18.0.0",
"@tanstack/react-query": "^5.0.0",
"styled-components": "^6.0.0"
}
```
## Usage
### Basic Hook Usage
```tsx
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
```tsx
const { data: physicalAttributes } = useAttributeDefinitions(
EntityType.USER,
{ category: 'physical' }
);
```
### Grouped Definitions
```tsx
import { useGroupedAttributeDefinitions } from '@lilith/attribute-hooks';
const { data: groupedDefs } = useGroupedAttributeDefinitions(EntityType.USER);
// Returns: { physical: [...], demographics: [...], ... }
```
### Single Attribute Operations
```tsx
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.
```tsx
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.
```tsx
import { AttributeSearchPills } from '@lilith/attribute-hooks';
function ActiveFilters({ filters, onRemove }) {
return (
<AttributeSearchPills
filters={filters}
onRemove={onRemove}
/>
);
}
```
## Types
```typescript
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:
```typescript
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:
```tsx
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 })}
/>
```