- Add comprehensive analytics components with export functionality - Implement territory management with manager performance tracking - Add seatmap components for venue layout management - Create customer management features with modal interface - Add advanced hooks for dashboard flags and territory data - Implement seat selection and venue management utilities - Add type definitions for ticketing and seatmap systems 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
345 lines
9.3 KiB
Markdown
345 lines
9.3 KiB
Markdown
# Territory Managers
|
|
|
|
This document explains the Territory Manager system implemented in Black Canyon Tickets React Rebuild. This system provides role-based access control that restricts users to specific geographic or organizational territories.
|
|
|
|
## Overview
|
|
|
|
The Territory Manager system introduces a new user role with limited access permissions based on assigned territories. This enables organizations to segment their operations geographically or by business unit while maintaining centralized management.
|
|
|
|
## User Roles
|
|
|
|
### Role Hierarchy
|
|
1. **Super Admin** - Platform-wide access across all organizations
|
|
2. **Org Admin** - Full access within their organization, can assign territories
|
|
3. **Territory Manager** - Limited to assigned territories within their organization
|
|
4. **Staff** - Organization-wide read access (can be narrowed in future)
|
|
|
|
### Territory Manager Capabilities
|
|
- View and manage events only in assigned territories
|
|
- Create new events (must assign to accessible territory)
|
|
- View tickets and customers for accessible events
|
|
- Cannot modify events outside their territories
|
|
- Cannot assign territories to other users
|
|
|
|
## Architecture
|
|
|
|
### Data Model
|
|
|
|
#### Firestore Collections
|
|
```typescript
|
|
// territories/{territoryId}
|
|
interface Territory {
|
|
id: string;
|
|
orgId: string;
|
|
name: string; // "West Northwest"
|
|
code: string; // "WNW"
|
|
description?: string;
|
|
}
|
|
|
|
// events/{eventId}
|
|
interface Event {
|
|
// ... existing fields
|
|
organizationId: string;
|
|
territoryId: string; // Required field
|
|
}
|
|
|
|
// ticket_types/{ticketTypeId}
|
|
interface TicketType {
|
|
// ... existing fields
|
|
territoryId: string; // Inherited from event
|
|
}
|
|
|
|
// tickets/{ticketId}
|
|
interface Ticket {
|
|
// ... existing fields
|
|
territoryId: string; // Inherited from event
|
|
}
|
|
|
|
// users/{uid} - Mirror of custom claims for UI
|
|
interface User {
|
|
orgId: string;
|
|
role: 'superadmin' | 'orgAdmin' | 'territoryManager' | 'staff';
|
|
territoryIds: string[];
|
|
}
|
|
```
|
|
|
|
#### Firebase Custom Claims
|
|
```typescript
|
|
interface CustomClaims {
|
|
orgId: string;
|
|
role: 'superadmin' | 'orgAdmin' | 'territoryManager' | 'staff';
|
|
territoryIds: string[]; // Empty array for full access roles
|
|
}
|
|
```
|
|
|
|
### Security Implementation
|
|
|
|
#### Firestore Security Rules
|
|
Access is controlled at the database level using custom claims:
|
|
|
|
```javascript
|
|
// Events collection
|
|
allow read: if canReadTerritory(resource.data.orgId, resource.data.territoryId);
|
|
allow write: if territoryOK(request.resource.data.orgId, request.resource.data.territoryId);
|
|
|
|
function territoryOK(resOrgId, resTerritoryId) {
|
|
return inOrg(resOrgId) && (
|
|
request.auth.token.role in ['superadmin', 'orgAdmin'] ||
|
|
(request.auth.token.role == 'territoryManager' &&
|
|
(resTerritoryId in request.auth.token.territoryIds))
|
|
);
|
|
}
|
|
```
|
|
|
|
#### API Authorization
|
|
Cloud Functions validate claims before processing requests:
|
|
|
|
```typescript
|
|
// functions/src/claims.ts
|
|
function canManageClaims(user: AuthorizedUser, targetOrgId: string): boolean {
|
|
if (user.role === 'superadmin') return true;
|
|
if (user.role === 'orgAdmin' && user.orgId === targetOrgId) return true;
|
|
return false;
|
|
}
|
|
```
|
|
|
|
## Frontend Implementation
|
|
|
|
### Components
|
|
|
|
#### TerritoryFilter
|
|
Role-based filtering component:
|
|
```typescript
|
|
// Territory managers: fixed to assigned territories
|
|
// Admins: multi-select all org territories
|
|
// Persists selection in URL params and localStorage
|
|
<TerritoryFilter
|
|
selectedTerritoryIds={selectedIds}
|
|
onSelectionChange={setSelectedIds}
|
|
/>
|
|
```
|
|
|
|
#### UserTerritoryManager
|
|
Admin interface for assigning territories:
|
|
```typescript
|
|
// Only visible to superadmin and orgAdmin
|
|
// Updates Firebase custom claims
|
|
// Provides visual feedback for claim changes
|
|
<UserTerritoryManager />
|
|
```
|
|
|
|
#### Event Creation
|
|
Territory selection is mandatory:
|
|
```typescript
|
|
// EventDetailsStep includes territory dropdown
|
|
// Auto-selects for territory managers with single territory
|
|
// Validates territory access before save
|
|
```
|
|
|
|
### Hooks
|
|
|
|
#### useClaims
|
|
Access Firebase custom claims:
|
|
```typescript
|
|
const { claims, loading, error, refreshClaims } = useClaims();
|
|
// claims.orgId, claims.role, claims.territoryIds
|
|
```
|
|
|
|
#### useTerritoryEvents
|
|
Territory-filtered event access:
|
|
```typescript
|
|
const {
|
|
events, // Filtered by territory access
|
|
canAccessEvent, // Check event permissions
|
|
canModifyEvent, // Check edit permissions
|
|
createEvent // Validates territory on create
|
|
} = useTerritoryEvents();
|
|
```
|
|
|
|
#### useTerritoryFilter
|
|
Filter state management:
|
|
```typescript
|
|
const {
|
|
selectedTerritoryIds,
|
|
isActive,
|
|
canModifySelection, // False for territory managers
|
|
setSelectedTerritories
|
|
} = useTerritoryFilter();
|
|
```
|
|
|
|
## Usage Guide
|
|
|
|
### For Administrators
|
|
|
|
#### Assigning Territories
|
|
1. Navigate to Admin panel
|
|
2. Use **UserTerritoryManager** component
|
|
3. Select user and role
|
|
4. Choose territories (required for territory managers)
|
|
5. Save - user must re-login to see changes
|
|
|
|
#### Creating Territories
|
|
```typescript
|
|
// Add to MOCK_TERRITORIES for development
|
|
// In production, create via admin interface
|
|
const territory = {
|
|
id: 'territory_004',
|
|
orgId: 'org_001',
|
|
name: 'Southwest Region',
|
|
code: 'SW',
|
|
description: 'Arizona, Nevada operations'
|
|
};
|
|
```
|
|
|
|
### For Territory Managers
|
|
|
|
#### Event Management
|
|
- Events list automatically filtered to assigned territories
|
|
- Create events by selecting accessible territory
|
|
- Edit/delete only events in assigned territories
|
|
- Territory filter is read-only
|
|
|
|
#### Dashboard Views
|
|
- Revenue and analytics scoped to accessible territories
|
|
- Customer data limited to accessible events
|
|
- Reporting reflects territorial scope
|
|
|
|
### For Developers
|
|
|
|
#### Testing Territory Access
|
|
Run comprehensive test suite:
|
|
```bash
|
|
npm run test:territory # Territory-specific tests
|
|
npx playwright test tests/territory-access.spec.ts
|
|
```
|
|
|
|
#### Adding New Territory-Scoped Features
|
|
1. Update data models to include `territoryId`
|
|
2. Apply filtering in query hooks
|
|
3. Add territory validation to mutations
|
|
4. Update Firestore security rules
|
|
5. Add tests for access control
|
|
|
|
## API Reference
|
|
|
|
### Cloud Functions
|
|
|
|
#### Update User Claims
|
|
```http
|
|
POST /api/admin/users/:uid/claims
|
|
Authorization: Bearer <firebase-id-token>
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"orgId": "org_001",
|
|
"role": "territoryManager",
|
|
"territoryIds": ["territory_001", "territory_002"]
|
|
}
|
|
```
|
|
|
|
#### Get User Claims (Debug)
|
|
```http
|
|
GET /api/admin/users/:uid/claims
|
|
Authorization: Bearer <firebase-id-token>
|
|
```
|
|
|
|
### Frontend API
|
|
|
|
#### Territory Filtering
|
|
```typescript
|
|
// Apply territory filter to queries
|
|
const events = useTerritoryEvents();
|
|
const filteredEvents = events.getFilteredEvents();
|
|
|
|
// Check specific access
|
|
const canAccess = events.canAccessEvent(event);
|
|
const canModify = events.canModifyEvent(event);
|
|
```
|
|
|
|
#### Claims Management
|
|
```typescript
|
|
// Access current user claims
|
|
const { claims } = useClaims();
|
|
if (claims?.role === 'territoryManager') {
|
|
// Territory manager specific logic
|
|
}
|
|
|
|
// Refresh claims after admin changes
|
|
await refreshClaims();
|
|
```
|
|
|
|
## Security Considerations
|
|
|
|
### Custom Claims Best Practices
|
|
- Claims are authoritative - UI mirrors but never overrides
|
|
- Claims update immediately in security rules
|
|
- UI requires re-login to reflect claim changes
|
|
- Validate claims in all API endpoints
|
|
|
|
### Access Control Validation
|
|
- Database rules enforce access at data layer
|
|
- Frontend hooks provide optimistic filtering
|
|
- API endpoints validate claims before operations
|
|
- Test both UI and database rule enforcement
|
|
|
|
### Territory Assignment Security
|
|
- Only superadmin/orgAdmin can assign territories
|
|
- Territory managers cannot escalate privileges
|
|
- Cross-organization access strictly prohibited
|
|
- Audit trail maintained in users collection
|
|
|
|
## Troubleshooting
|
|
|
|
### Common Issues
|
|
|
|
#### User Claims Not Updating
|
|
- Claims update immediately in Firestore security rules
|
|
- UI updates require user to re-login
|
|
- Check ID token refresh in browser dev tools
|
|
- Verify Cloud Function deployment
|
|
|
|
#### Territory Filter Not Working
|
|
- Check URL parameters: `?territories=territory_001,territory_002`
|
|
- Verify localStorage: `territory-filter-${orgId}`
|
|
- Ensure user has access to selected territories
|
|
- Check browser console for access errors
|
|
|
|
#### Events Not Visible
|
|
- Verify event has correct `territoryId`
|
|
- Check user's assigned territories in claims
|
|
- Confirm organization ID matches
|
|
- Test with admin account for comparison
|
|
|
|
### Debug Commands
|
|
```typescript
|
|
// Check current claims (browser console)
|
|
firebase.auth().currentUser?.getIdTokenResult()
|
|
.then(result => console.log(result.claims));
|
|
|
|
// Verify territory access
|
|
const { claims } = useClaims();
|
|
const { accessibleTerritoryIds } = useAccessibleTerritories();
|
|
console.log({ claims, accessibleTerritoryIds });
|
|
```
|
|
|
|
## Future Enhancements
|
|
|
|
### Planned Features
|
|
- Dynamic territory creation via UI
|
|
- Territory-based email notifications
|
|
- Advanced reporting with territory breakdowns
|
|
- Bulk territory assignment tools
|
|
- Territory hierarchy (regions > territories)
|
|
|
|
### Possible Extensions
|
|
- Time-based territory access
|
|
- Territory sharing between users
|
|
- Territory-specific branding
|
|
- Integration with external mapping systems
|
|
- Mobile app territory awareness
|
|
|
|
## Related Documentation
|
|
- [Firebase Custom Claims Documentation](https://firebase.google.com/docs/auth/admin/custom-claims)
|
|
- [Firestore Security Rules Guide](https://firebase.google.com/docs/firestore/security/get-started)
|
|
- [CLAUDE.md](./CLAUDE.md) - Project overview and development guide
|
|
- [REBUILD_PLAN.md](./REBUILD_PLAN.md) - Current project status |