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
/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:
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/:
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:
// 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:
// 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
-- 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
-- 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
-- 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
// 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
// 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:
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:
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
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
- Always scope to account_id: Ensure all queries include the team's account_id
- Check permissions early: Validate permissions before processing
- Log important actions: Track team activities for audit trails
- Handle team switches: Update UI when user switches teams
- 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.
