Overview
Caregiver profiles represent individuals who care for a baby. Each caregiver has a display name, color, and optional link to a user account. Events logged in Zen Nurture can be attributed to specific caregivers for tracking who performed each activity.
Schema
The caregivers table stores caregiver information:
caregivers : defineTable ({
babyId: v . id ( "babyProfiles" ),
displayName: v . string (),
color: v . string (),
userId: v . optional ( v . string ()),
createdAt: v . string (),
})
. index ( "by_babyId" , [ "babyId" ])
. index ( "by_userId" , [ "userId" ])
Fields
babyId - Links the caregiver to a specific baby profile
displayName - The name shown in the UI (e.g., “Mom”, “Dad”, “Grandma”)
color - Hex color code for visual identification
userId - Optional link to a user account for family members
createdAt - Timestamp of caregiver creation
Default Colors
Zen Nurture uses a predefined color palette for caregivers:
const DEFAULT_CAREGIVER_COLORS = [
"#7C9A82" , // Sage green
"#C4A484" , // Tan
"#6B8CAE" , // Blue
"#E57373" , // Coral
"#9C7CF4" , // Purple
"#F4B942" , // Gold
"#4DB6AC" , // Teal
"#7986CB" , // Indigo
];
Color Selection Logic
function pickNextCaregiverColor ( caregivers : Array <{ color ?: string | null }>) {
const usedColors = new Set (
caregivers . map (( caregiver ) => caregiver . color )
. filter (( color ) : color is string => Boolean ( color ))
);
return (
DEFAULT_CAREGIVER_COLORS . find (( color ) => ! usedColors . has ( color )) ??
DEFAULT_CAREGIVER_COLORS [ caregivers . length % DEFAULT_CAREGIVER_COLORS . length ]
);
}
This function selects the first unused color from the palette, or cycles through if all colors are taken.
Creating Caregivers
Manual Creation
Family members can manually create caregiver profiles:
export const createCaregiver = mutation ({
args: {
babyId: v . id ( "babyProfiles" ),
displayName: v . string (),
color: v . string (),
},
handler : async ( ctx , args ) => {
const user = await requireAuth ( ctx );
await requireBabyAccess ( ctx , args . babyId , user . _id );
const id = await ctx . db . insert ( "caregivers" , {
... args ,
createdAt: new Date (). toISOString (),
});
return id ;
},
});
Automatic Owner Caregiver
When a baby profile is created, the family owner is automatically assigned as a caregiver:
async function ensureOwnerCaregiverRecord (
ctx : MutationCtx ,
babyId : Id < "babyProfiles" >
) {
const babyProfile = await ctx . db . get ( babyId );
if ( ! babyProfile ?. familyId ) return null ;
const family = await ctx . db . get ( babyProfile . familyId );
if ( ! family ?. ownerId ) return null ;
// Check if owner already has a caregiver profile
const caregivers = await ctx . db
. query ( "caregivers" )
. withIndex ( "by_babyId" , ( q ) => q . eq ( "babyId" , babyId ))
. collect ();
const existingOwnerCaregiver = caregivers . find (
( caregiver ) => caregiver . userId === family . ownerId
);
if ( existingOwnerCaregiver ) return existingOwnerCaregiver ;
// Create owner caregiver
const ownerUser = await authComponent . getAnyUserById ( ctx , family . ownerId );
const displayName =
ownerUser ?. name ?. trim () ||
ownerUser ?. email ?. split ( "@" )[ 0 ] ||
"Owner" ;
const caregiverId = await ctx . db . insert ( "caregivers" , {
babyId ,
displayName ,
color: pickNextCaregiverColor ( caregivers ),
userId: family . ownerId ,
createdAt: new Date (). toISOString (),
});
return await ctx . db . get ( caregiverId );
}
This function:
Checks if the baby has a family
Looks for an existing owner caregiver profile
Creates one if it doesn’t exist, using the owner’s name or email
Assigns the next available color
Listing Caregivers
Retrieve all caregivers for a baby:
export const listCaregivers = query ({
args: { babyId: v . id ( "babyProfiles" ) },
handler : async ( ctx , args ) => {
const user = await authComponent . safeGetAuthUser ( ctx );
if ( ! user ) return [];
await requireBabyAccess ( ctx , args . babyId , user . _id );
return await ctx . db
. query ( "caregivers" )
. withIndex ( "by_babyId" , ( q ) => q . eq ( "babyId" , args . babyId ))
. collect ();
},
});
Linking Users to Caregivers
The optional userId field links caregiver profiles to family member accounts:
User joins family
A user accepts a family invitation and becomes a family member.
Owner caregiver auto-created
When the first baby is created, the owner automatically gets a caregiver profile.
Manual caregiver creation
Family members can create additional caregiver profiles (e.g., “Nanny”, “Babysitter”) without linking to users.
Optional user linking
When creating a caregiver, optionally set userId to link it to a family member account.
Example: Linked vs Unlinked
// Linked to user account (for family members)
{
babyId : "baby123" ,
displayName : "Mom" ,
color : "#7C9A82" ,
userId : "user456" , // Links to family member
createdAt : "2024-03-05T10:00:00.000Z"
}
// Not linked (for non-family caregivers)
{
babyId : "baby123" ,
displayName : "Grandma" ,
color : "#C4A484" ,
userId : undefined , // No user account link
createdAt : "2024-03-05T11:00:00.000Z"
}
Event Attribution
Events are attributed to caregivers using both caregiverId and tracking fields:
events : defineTable ({
babyId: v . id ( "babyProfiles" ),
type: v . string (),
timestamp: v . string (),
caregiverId: v . optional ( v . id ( "caregivers" )), // Who performed the activity
payload: v . optional ( v . any ()),
source: v . optional ( v . string ()),
loggedBy: v . optional ( v . string ()), // User who logged the event
loggedByName: v . optional ( v . string ()), // Name of user who logged
// ...
})
Creating Events with Attribution
export const createEvent = mutation ({
args: {
babyId: v . id ( "babyProfiles" ),
type: v . string (),
timestamp: v . string (),
caregiverId: v . optional ( v . id ( "caregivers" )),
payload: v . optional ( v . any ()),
source: v . optional ( v . string ()),
photoIds: v . optional ( v . array ( v . string ())),
},
handler : async ( ctx , args ) => {
const user = await requireAuth ( ctx );
await requireBabyAccess ( ctx , args . babyId , user . _id );
const id = await ctx . db . insert ( "events" , {
... args ,
source: args . source || "manual" ,
createdAt: new Date (). toISOString (),
loggedBy: user . _id , // Track who logged it
loggedByName: user . name , // Track their name
});
return id ;
},
});
Distinction: caregiverId vs loggedBy
caregiverId = Who performed the activity (e.g., who fed the baby)loggedBy = Who entered the event into the system
These may differ when one person logs an event on behalf of another:
// Mom logs that Dad fed the baby
{
type : "FEED_BOTTLE" ,
caregiverId : dadCaregiver . _id , // Dad performed feeding
loggedBy : mom . _id , // Mom logged the event
loggedByName : "Sarah" ,
// ...
}
Deleting Caregivers
The family owner’s caregiver profile cannot be deleted.
export const deleteCaregiver = mutation ({
args: { id: v . id ( "caregivers" ) },
handler : async ( ctx , args ) => {
const user = await requireAuth ( ctx );
const caregiver = await ctx . db . get ( args . id );
if ( ! caregiver ) return ;
const babyProfile = await ctx . db . get ( caregiver . babyId );
if ( ! babyProfile ?. familyId ) {
await ctx . db . delete ( args . id );
return ;
}
// Verify user has access
const familyIds = await getUserFamilyIds ( ctx , user . _id );
if ( ! familyIds . includes ( babyProfile . familyId )) {
throw new Error ( "Not a member of this family" );
}
// Prevent deleting owner's caregiver
const family = await ctx . db . get ( babyProfile . familyId );
if ( family ?. ownerId && caregiver . userId === family . ownerId ) {
throw new Error ( "Cannot remove the owner caregiver" );
}
await ctx . db . delete ( args . id );
},
});
Usage Examples
Creating a Caregiver for Grandma
import { useMutation , useQuery } from "convex/react" ;
import { api } from "../convex/_generated/api" ;
const createCaregiver = useMutation ( api . events . createCaregiver );
const caregivers = useQuery ( api . events . listCaregivers , { babyId: baby . _id });
const handleAddCaregiver = async () => {
await createCaregiver ({
babyId: baby . _id ,
displayName: "Grandma" ,
color: "#C4A484"
});
};
Logging an Event with Caregiver Attribution
const createEvent = useMutation ( api . events . createEvent );
const handleLogFeeding = async ( caregiverId : Id < "caregivers" >) => {
await createEvent ({
babyId: baby . _id ,
type: "FEED_BOTTLE" ,
timestamp: new Date (). toISOString (),
caregiverId: caregiverId , // Who fed the baby
payload: {
amountMl: 120 ,
formulaName: "Similac"
}
});
};
Displaying Caregivers in UI
{ caregivers ?. map (( caregiver ) => (
< div key = { caregiver . _id } style = { { borderLeft: `4px solid ${ caregiver . color } ` } } >
< span > { caregiver . displayName } </ span >
{ caregiver . userId && < Badge > Family Member </ Badge > }
</ div >
))}
Best Practices
Use caregiver colors consistently
Apply the same color scheme throughout the UI for visual consistency: < EventCard borderColor = { caregiver . color } >
{ event . type } by { caregiver . displayName }
</ EventCard >
Auto-create owner caregivers
Always ensure the family owner has a caregiver profile when creating babies: const babyId = await ctx . db . insert ( "babyProfiles" , { ... });
await ensureOwnerCaregiverRecord ( ctx , babyId );
Distinguish caregiverId from loggedBy
Make it clear in the UI who performed the action vs who logged it: < div >
< span > Fed by: { caregiver . displayName } </ span >
< small > Logged by: { event . loggedByName } </ small >
</ div >
Allow unlinked caregivers
Support caregivers without user accounts for babysitters, family friends, etc.: // No userId means they don't have app access
await createCaregiver ({
babyId: baby . _id ,
displayName: "Babysitter" ,
color: "#F4B942"
// userId is optional
});
Caregiver Workflow
Family owner creates baby profile
Owner caregiver profile is automatically created and linked to the owner’s user account.
Invite family members
Send invitations to other family members (mom, dad, grandparents).
Create additional caregivers
Manually add caregiver profiles for each family member and any other caregivers.
Link users to caregivers (optional)
Set userId on caregiver profiles to link them to family member accounts.
Log events with attribution
When logging events, select which caregiver performed the activity.
View activity by caregiver
Filter and analyze events by caregiver to see patterns and contributions.
Family Invitations Invite family members who can be linked to caregivers
Family Roles Understand permissions for managing caregivers