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
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>
129 lines
4.0 KiB
TypeScript
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;
|
|
},
|
|
};
|