# league-system > Use when working with leagues, league rules, members, invites, or role permissions - Author: Mason Campbell - Repository: zekkathura/10K_Project - Version: 20260124180122 - Stars: 0 - Forks: 0 - Last Updated: 2026-02-07 - Source: https://github.com/zekkathura/10K_Project - Web: https://mule.run/skillshub/@@zekkathura/10K_Project~league-system:20260124180122 --- --- name: league-system description: Use when working with leagues, league rules, members, invites, or role permissions --- # League System The League feature replaces House Rules with guild-like functionality. Users create or join leagues to manage custom game rules. ## Core Concepts ### Membership - **Exclusive**: Users can only be in ONE league at a time - **Invite-only**: No public join codes - moderators/owners invite players they've played with - **Registered users only**: No guest support for leagues ### Permission Roles (3-tier) | Role | View Rules | Add/Revoke Rules | Invite | Change Roles | Kick | Transfer Ownership | Edit League | |------|------------|------------------|--------|--------------|------|-------------------|-------------| | **Owner** | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | **Moderator** | ✓ | ✓ | ✓ | ✓ (not owner) | ✗ | ✗ | ✗ | | **Member** | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ### Rule Lifecycle (Honor System) Rules are managed in the League screen. No in-app voting - voting happens in person. **Adding a Rule:** 1. Moderator/Owner taps "Add Rule" 2. Enters rule text and confirms honor-system questions: - "Were 2+ league members present?" (Yes/No) - "Did majority vote in favor?" (Yes/No) 3. Rule created immediately with `status='active'` **Revoking a Rule:** 1. Moderator/Owner taps rule → "Revoke" 2. Same honor-system confirmations 3. Rule status changes to `'revoked'` ## Key Files ### Database - `src/lib/leagueDatabase.ts` - All league database operations - `src/lib/types.ts` - League, LeagueMember, LeagueRule, LeagueInvite, LeagueActivity types ### Screens & Modals - `src/screens/LeagueScreen.tsx` - Main league screen (tabs: Rules, Members, Activity, Settings) - `src/screens/CreateLeagueModal.tsx` - Create new league - `src/screens/InvitePlayerModal.tsx` - Invite co-players to league - `src/screens/InvitePendingModal.tsx` - Accept/decline incoming invites - `src/screens/AddRuleModal.tsx` - Add new rule with honor-system confirmations - `src/screens/RevokeRuleModal.tsx` - Revoke existing rule - `src/screens/ChangeRoleModal.tsx` - Change member roles (with kick/transfer) - `src/screens/EditLeagueModal.tsx` - Edit league name/description - `src/screens/TransferOwnershipModal.tsx` - Transfer ownership to another member ### Migrations (run in order) ``` database/migrations/006_league_system.sql # Tables database/migrations/007_league_rls.sql # Functions + RLS policies database/migrations/007b_league_grants.sql # Table permissions database/migrations/008_league_game_association.sql # Game linking database/migrations/009_league_realtime.sql # Realtime subscriptions database/migrations/010_migrate_extra_rules_to_league.sql # Data migration ``` ## Database Tables | Table | Purpose | |-------|---------| | `leagues` | Core league info (name, description, status) | | `league_members` | User membership (one league per user via UNIQUE constraint) | | `league_rules` | League-specific rules (replaces extra_rules) | | `league_invites` | Pending invitations | | `league_activity_log` | Audit trail for league events | ## Common Operations ### Using leagueDatabase.ts ```typescript import { getUserLeagueMembership, getPendingInvitesForUser, createLeague, invitePlayer, respondToInvite, leaveLeague, addRule, revokeRule, changeMemberRole, transferOwnership, kickMember, getLeagueMembers, getLeagueRules, getLeagueActivityLog, } from '../lib/leagueDatabase'; // Get user's league info const { data, error } = await getUserLeagueMembership(userId); // Create a league (user becomes owner) const result = await createLeague(userId, 'League Name', 'Description'); // Add a rule const result = await addRule( leagueId, 'Rule text here', 'Proposer Name', 'Approver1, Approver2', true, // hadMinMembers true, // hadMajorityVote gameId // optional - link to game where proposed ); // Change member role const result = await changeMemberRole( leagueId, targetUserId, 'moderator', // 'owner' | 'moderator' | 'member' actorName, targetName ); ``` ## UI Patterns ### LeagueScreen Tabs - **Rules**: List of active rules, search, sort by # or date, show revoked toggle - **Members**: Member list with role badges, invite button (moderators+) - **Activity**: Event log (joins, leaves, rule changes, invites) - **Settings**: Edit league, transfer ownership, leave/delete league ### Role-Based UI Visibility ```typescript const canModify = userRole === 'owner' || userRole === 'moderator'; const isOwner = userRole === 'owner'; // Show add rule button only for moderators+ {canModify && } // Show kick button only for owner {isOwner && } ``` ### Confirmation Pattern Destructive actions (kick, transfer, role change) use two-step confirmation: ```typescript const [confirmAction, setConfirmAction] = useState(false); const handleAction = async () => { if (!confirmAction) { setConfirmAction(true); // First tap shows confirmation return; } // Second tap executes await performAction(); }; ``` ## Edge Cases | Scenario | Handling | |----------|----------| | User not in league | Show welcome screen + pending invites | | Invite player in other league | Show disabled in list with league name | | Owner tries to leave | Must transfer ownership first | | Moderator changes owner role | Blocked - only owner can transfer | | Delete league | Only owner, removes all members/rules | ## Realtime Subscriptions League data uses Supabase realtime for live updates: ```typescript // In LeagueScreen.tsx useEffect(() => { const channel = supabase .channel(`league-${leagueId}`) .on('postgres_changes', { event: '*', schema: 'public', table: 'league_rules', filter: `league_id=eq.${leagueId}` }, handleRulesChange) .subscribe(); return () => { supabase.removeChannel(channel); }; }, [leagueId]); ```