Files
maternal-app/maternal-web/lib/api/families.ts
Andrei 40dbb2287a
Some checks failed
ParentFlow CI/CD Pipeline / Backend Tests (push) Has been cancelled
ParentFlow CI/CD Pipeline / Frontend Tests (push) Has been cancelled
ParentFlow CI/CD Pipeline / Security Scanning (push) Has been cancelled
ParentFlow CI/CD Pipeline / Build Docker Images (map[context:maternal-app/maternal-app-backend dockerfile:Dockerfile.production name:backend]) (push) Has been cancelled
ParentFlow CI/CD Pipeline / Build Docker Images (map[context:maternal-web dockerfile:Dockerfile.production name:frontend]) (push) Has been cancelled
ParentFlow CI/CD Pipeline / Deploy to Development (push) Has been cancelled
ParentFlow CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / Lint and Test (push) Has been cancelled
CI/CD Pipeline / E2E Tests (push) Has been cancelled
CI/CD Pipeline / Build Application (push) Has been cancelled
feat: Implement comprehensive onboarding improvements with role-based family invites
This commit adds a complete onboarding improvements system including progress
tracking, streamlined UI, and role-based family invitation system.

## Backend Changes

### Database Migrations
- Add onboarding tracking fields to users table (onboarding_completed, onboarding_step, onboarding_data)
- Add role-based invite codes to families table (parent/caregiver/viewer codes with expiration)
- Add indexes for fast invite code lookups

### User Preferences Module
- Add UserPreferencesController with onboarding endpoints
- Add UserPreferencesService with progress tracking methods
- Add UpdateOnboardingProgressDto for validation
- Endpoints: GET/PUT /api/v1/preferences/onboarding, POST /api/v1/preferences/onboarding/complete

### Families Module - Role-Based Invites
- Add generateRoleInviteCode() - Generate role-specific codes with expiration
- Add getRoleInviteCodes() - Retrieve all active codes for a family
- Add joinFamilyWithRoleCode() - Join family with automatic role assignment
- Add revokeRoleInviteCode() - Revoke specific role invite codes
- Add sendEmailInvite() - Generate code and send email invitation
- Endpoints: POST/GET/DELETE /api/v1/families/:id/invite-codes, POST /api/v1/families/join-with-role, POST /api/v1/families/:id/email-invite

### Email Service
- Add sendFamilyInviteEmail() - Send role-based invitation emails
- Beautiful HTML templates with role badges (👨‍👩‍👧 parent, 🤝 caregiver, 👁️ viewer)
- Role-specific permission descriptions
- Graceful fallback if email sending fails

### Auth Service
- Fix duplicate family creation bug in joinFamily()
- Ensure users only join family once during onboarding

## Frontend Changes

### Onboarding Page
- Reduce steps from 5 to 4 (combined language + measurements)
- Replace card-based selection with dropdown selectors
- Add automatic progress saving after each step
- Add progress restoration on page mount
- Extract FamilySetupStep into reusable component

### Family Page
- Add RoleInvitesSection component with accordion UI
- Generate/view/copy/regenerate/revoke controls for each role
- Send email invites directly from UI
- Display expiration dates (e.g., "Expires in 5 days")
- Info tooltips explaining role permissions
- Only visible to users with parent role

### API Client
- Add role-based invite methods to families API
- Add onboarding progress methods to users API
- TypeScript interfaces for all new data structures

## Features

 Streamlined 4-step onboarding with dropdown selectors
 Automatic progress save/restore across sessions
 Role-based family invites (parent/caregiver/viewer)
 Beautiful email invitations with role descriptions
 Automatic role assignment when joining with invite codes
 Granular permission control per role
 Email fallback if sending fails
 All changes tested and production-ready

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-09 15:25:16 +00:00

129 lines
4.0 KiB
TypeScript

import apiClient from './client';
export interface Family {
id: string;
name: string;
shareCode: string;
createdBy: string;
subscriptionTier: string;
members?: FamilyMember[];
}
export interface FamilyMember {
id: string;
userId: string;
familyId: string;
role: 'parent' | 'caregiver' | 'viewer';
permissions: any;
user?: {
id: string;
name: string;
email: string;
};
}
export interface InviteMemberData {
email: string;
role: 'parent' | 'caregiver' | 'viewer';
}
export interface JoinFamilyData {
shareCode: string;
}
export const familiesApi = {
// Create a new family
createFamily: async (data: { name: string }): Promise<Family> => {
const response = await apiClient.post('/api/v1/families', data);
return response.data.data.family;
},
// Get a specific family
getFamily: async (familyId: string): Promise<Family> => {
const response = await apiClient.get(`/api/v1/families/${familyId}`);
return response.data.data.family;
},
// Get family members
getFamilyMembers: async (familyId: string): Promise<FamilyMember[]> => {
const response = await apiClient.get(`/api/v1/families/${familyId}/members`);
return response.data.data.members;
},
// Invite a family member
inviteMember: async (familyId: string, data: InviteMemberData): Promise<any> => {
const response = await apiClient.post(`/api/v1/families/invite?familyId=${familyId}`, data);
return response.data.data.invitation;
},
// Join a family using share code
joinFamily: async (data: JoinFamilyData): Promise<FamilyMember> => {
const response = await apiClient.post('/api/v1/families/join', data);
return response.data.data.member;
},
// Update member role
updateMemberRole: async (familyId: string, userId: string, role: string): Promise<FamilyMember> => {
const response = await apiClient.patch(`/api/v1/families/${familyId}/members/${userId}/role`, { role });
return response.data.data.member;
},
// Remove a family member
removeMember: async (familyId: string, userId: string): Promise<void> => {
await apiClient.delete(`/api/v1/families/${familyId}/members/${userId}`);
},
// Generate a new share code for the family
generateShareCode: async (familyId: string): Promise<{ code: string; expiresAt: Date }> => {
const response = await apiClient.post(`/api/v1/families/${familyId}/share-code`);
return response.data.data;
},
// Generate role-based invite code
generateRoleInviteCode: async (
familyId: string,
role: 'parent' | 'caregiver' | 'viewer',
expiryDays?: number
): Promise<{ code: string; expiresAt: Date; role: string }> => {
const response = await apiClient.post(`/api/v1/families/${familyId}/invite-codes`, {
role,
expiryDays,
});
return response.data.data;
},
// Get all active role-based invite codes
getRoleInviteCodes: async (familyId: string): Promise<{
parent?: { code: string; expiresAt: Date };
caregiver?: { code: string; expiresAt: Date };
viewer?: { code: string; expiresAt: Date };
}> => {
const response = await apiClient.get(`/api/v1/families/${familyId}/invite-codes`);
return response.data.data;
},
// Join family with role-based invite code
joinFamilyWithRoleCode: async (inviteCode: string): Promise<FamilyMember> => {
const response = await apiClient.post('/api/v1/families/join-with-role', { inviteCode });
return response.data.data.member;
},
// Revoke a role-based invite code
revokeRoleInviteCode: async (familyId: string, role: 'parent' | 'caregiver' | 'viewer'): Promise<void> => {
await apiClient.delete(`/api/v1/families/${familyId}/invite-codes/${role}`);
},
// Send email invite with role-based code
sendEmailInvite: async (
familyId: string,
email: string,
role: 'parent' | 'caregiver' | 'viewer'
): Promise<{ code: string; expiresAt: Date; emailSent: boolean }> => {
const response = await apiClient.post(`/api/v1/families/${familyId}/email-invite`, {
email,
role,
});
return response.data.data;
},
};