Skip to main content

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:
convex/events.ts
const DEFAULT_CAREGIVER_COLORS = [
  "#7C9A82",  // Sage green
  "#C4A484",  // Tan
  "#6B8CAE",  // Blue
  "#E57373",  // Coral
  "#9C7CF4",  // Purple
  "#F4B942",  // Gold
  "#4DB6AC",  // Teal
  "#7986CB",  // Indigo
];

Color Selection Logic

convex/events.ts
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:
convex/events.ts
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:
convex/events.ts
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:
convex/events.ts
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:
1

User joins family

A user accepts a family invitation and becomes a family member.
2

Owner caregiver auto-created

When the first baby is created, the owner automatically gets a caregiver profile.
3

Manual caregiver creation

Family members can create additional caregiver profiles (e.g., “Nanny”, “Babysitter”) without linking to users.
4

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

convex/events.ts
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.
convex/events.ts
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

Apply the same color scheme throughout the UI for visual consistency:
<EventCard borderColor={caregiver.color}>
  {event.type} by {caregiver.displayName}
</EventCard>
Always ensure the family owner has a caregiver profile when creating babies:
const babyId = await ctx.db.insert("babyProfiles", {...});
await ensureOwnerCaregiverRecord(ctx, babyId);
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>
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

1

Family owner creates baby profile

Owner caregiver profile is automatically created and linked to the owner’s user account.
2

Invite family members

Send invitations to other family members (mom, dad, grandparents).
3

Create additional caregivers

Manually add caregiver profiles for each family member and any other caregivers.
4

Link users to caregivers (optional)

Set userId on caregiver profiles to link them to family member accounts.
5

Log events with attribution

When logging events, select which caregiver performed the activity.
6

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

Build docs developers (and LLMs) love