Skip to content

add-team-feature

Access: /template add-team-feature or /t add-team-feature

Creates features specifically designed for team collaboration within MakerKit's multi-tenant architecture, with proper scoping, permissions, and team context handling.

What It Does

The add-team-feature command creates team-scoped features that:

  • Respect team boundaries and data isolation
  • Implement role-based access control
  • Handle team member permissions
  • Support team-wide settings and preferences
  • Include team activity tracking

Usage

bash
/template add-team-feature "Description"
# or
/t add-team-feature "Description"

When prompted, provide:

  • Feature name and purpose
  • Required team permissions
  • Roles that can access the feature
  • Whether it needs owner approval
  • Activity tracking requirements

Prerequisites

  • A MakerKit project with team functionality
  • Understanding of MakerKit's team model
  • Commercial MakerKit license from MakerKit

What Gets Created

1. Team-Scoped Database Tables

With proper foreign keys and RLS policies:

sql
create table public.team_projects (
  id uuid default gen_random_uuid() primary key,
  account_id uuid not null references public.accounts(id) on delete cascade,
  name text not null,
  description text,
  created_by uuid references auth.users(id),
  assigned_to uuid references public.accounts_account_members(user_id),
  status text not null default 'active',
  created_at timestamptz default now()
);

2. Team-Aware Server Actions

Located in apps/web/app/home/[account]/[feature]/_lib/server/:

typescript
export const createTeamProjectAction = enhanceAction(
  async (data, user) => {
    // Verify team membership and permissions
    const hasPermission = await checkTeamPermission(
      user.id,
      data.accountId,
      'projects.create'
    );
    
    if (!hasPermission) {
      throw new Error('Insufficient permissions');
    }
    
    // Create with team context
    const { data: project } = await client
      .from('team_projects')
      .insert({
        ...data,
        created_by: user.id,
      })
      .select()
      .single();
    
    // Log team activity
    await logTeamActivity({
      account_id: data.accountId,
      user_id: user.id,
      action: 'project.created',
      resource_id: project.id,
    });
    
    return project;
  },
  {
    auth: true,
    schema: CreateTeamProjectSchema,
  }
);

3. Team Member Components

UI components that handle team context:

typescript
// Team member selector
export function TeamMemberSelect({ 
  accountId, 
  onSelect 
}: { 
  accountId: string;
  onSelect: (userId: string) => void;
}) {
  const { data: members } = useTeamMembers(accountId);
  
  return (
    <Select onValueChange={onSelect}>
      <SelectTrigger>
        <SelectValue placeholder="Assign to team member" />
      </SelectTrigger>
      <SelectContent>
        {members?.map((member) => (
          <SelectItem key={member.user_id} value={member.user_id}>
            <div className="flex items-center gap-2">
              <Avatar className="h-6 w-6">
                <AvatarImage src={member.picture_url} />
                <AvatarFallback>
                  {member.display_name?.[0]}
                </AvatarFallback>
              </Avatar>
              <span>{member.display_name}</span>
              <Badge variant="outline" className="ml-auto">
                {member.role}
              </Badge>
            </div>
          </SelectItem>
        ))}
      </SelectContent>
    </Select>
  );
}

4. Team Activity Tracking

Automatic logging of team actions:

typescript
// Activity log table
create table public.team_activity_log (
  id uuid default gen_random_uuid() primary key,
  account_id uuid not null references public.accounts(id) on delete cascade,
  user_id uuid references auth.users(id),
  action text not null,
  resource_type text,
  resource_id uuid,
  metadata jsonb default '{}',
  created_at timestamptz default now()
);

// Activity feed component
export function TeamActivityFeed({ accountId }: { accountId: string }) {
  const { data: activities } = useTeamActivities(accountId);
  
  return (
    <div className="space-y-4">
      {activities?.map((activity) => (
        <ActivityItem key={activity.id} activity={activity} />
      ))}
    </div>
  );
}

Team Permission Patterns

Role-Based Access

sql
-- Only admins and owners can manage
create policy "Admins can manage projects"
  on public.team_projects
  for all
  to authenticated
  using (
    public.has_role_on_account(account_id, 'admin'::account_role) OR
    public.has_role_on_account(account_id, 'owner'::account_role)
  );

-- Members can view
create policy "Members can view projects"
  on public.team_projects
  for select
  to authenticated
  using (
    public.has_role_on_account(account_id)
  );

Permission-Based Access

sql
-- Specific permission required
create policy "Can create with permission"
  on public.team_projects
  for insert
  to authenticated
  with check (
    public.has_permission(
      auth.uid(), 
      account_id, 
      'projects.create'::app_permissions
    )
  );

Owner-Only Operations

sql
-- Only resource owner can delete
create policy "Owner can delete"
  on public.team_projects
  for delete
  to authenticated
  using (
    created_by = auth.uid() OR
    public.is_account_owner(account_id, auth.uid())
  );

Team Context Handling

URL-Based Team Context

typescript
// Page component with team context
export default async function TeamProjectsPage({
  params,
}: {
  params: { account: string };
}) {
  const accountId = params.account;
  const projects = await getTeamProjects(accountId);
  
  return (
    <TeamProjectsList 
      accountId={accountId}
      projects={projects}
    />
  );
}

Team Switching

typescript
// Handle team context switches
export function useTeamContext() {
  const pathname = usePathname();
  const accountId = pathname.split('/')[2]; // Extract from URL
  
  return {
    accountId,
    switchTeam: (newAccountId: string) => {
      router.push(`/home/${newAccountId}/projects`);
    },
  };
}

Common Team Features

1. Shared Resources

  • Documents/files accessible by team
  • Shared calendars and events
  • Team-wide settings and preferences

2. Collaboration Tools

  • Comments and discussions
  • Real-time notifications
  • Activity feeds and audit logs

3. Team Management

  • Member invitations
  • Role assignments
  • Permission management
  • Team billing and quotas

Real-Time Updates

Using Supabase subscriptions for team updates:

typescript
export function useTeamProjectsSubscription(accountId: string) {
  const queryClient = useQueryClient();
  
  useEffect(() => {
    const channel = supabase
      .channel(`team-projects-${accountId}`)
      .on(
        'postgres_changes',
        {
          event: '*',
          schema: 'public',
          table: 'team_projects',
          filter: `account_id=eq.${accountId}`,
        },
        (payload) => {
          queryClient.invalidateQueries({
            queryKey: ['team-projects', accountId],
          });
        }
      )
      .subscribe();
    
    return () => {
      supabase.removeChannel(channel);
    };
  }, [accountId]);
}

Team Notifications

Send notifications to team members:

typescript
async function notifyTeamMembers({
  accountId,
  excludeUserId,
  notification,
}: {
  accountId: string;
  excludeUserId?: string;
  notification: {
    title: string;
    body: string;
    link?: string;
  };
}) {
  // Get team members
  const { data: members } = await client
    .from('accounts_account_members')
    .select('user_id')
    .eq('account_id', accountId)
    .neq('user_id', excludeUserId);
  
  // Create notifications
  const notifications = members.map((member) => ({
    user_id: member.user_id,
    ...notification,
    created_at: new Date().toISOString(),
  }));
  
  await client.from('notifications').insert(notifications);
}

Testing Team Features

Test Different Roles

typescript
describe('Team Projects', () => {
  it('allows owners to create projects', async () => {
    const owner = await createTestUser({ role: 'owner' });
    const result = await createTeamProjectAction(data, owner);
    expect(result).toBeDefined();
  });
  
  it('prevents members from deleting', async () => {
    const member = await createTestUser({ role: 'member' });
    await expect(
      deleteTeamProjectAction(projectId, member)
    ).rejects.toThrow();
  });
});

Best Practices

  1. Always scope to account_id: Ensure all queries include the team's account_id
  2. Check permissions early: Validate permissions before processing
  3. Log important actions: Track team activities for audit trails
  4. Handle team switches: Update UI when user switches teams
  5. Test with multiple roles: Ensure features work for all team roles

License Requirement

Important: This command requires a commercial MakerKit license from https://makerkit.dev?atp=MqaGgc MakerKit is a premium SaaS starter kit and requires proper licensing for commercial use.

Built with ❤️ for the AI Coding community, by Praney Behl