Skip to main content

Multi-Tier Access System

Cabina implements a 4-level hierarchy that provides granular access control while maintaining simplicity for end users. This system powers the B2B2C event model.

The 4 Levels


Level 1: MASTER (Eagle-Eye)

The platform owner (Leo) with complete control over all aspects.

Capabilities

Partner Management

  • Create/edit/deactivate partners
  • Assign credits to partners
  • View all partner transactions
  • Approve/reject partner applications

Event Oversight

  • View ALL events across all partners
  • Monitor credit usage globally
  • Access event analytics
  • Override event settings

Platform Control

  • Manage AI style library
  • Configure global settings
  • Access system logs
  • Modify pricing tiers

B2C Management

  • Manage direct users
  • Process refunds
  • View purchase history
  • Handle support tickets

Dashboard Access

// src/components/dashboards/Admin.tsx:27
export const Admin: React.FC<AdminProps> = ({ onBack }) => {
  const [view, setView] = useState<
    'overview' | 'partners' | 'b2c' | 'styles' | 'logs' | 'settings'
  >('overview');
  
  // Master sees everything
  const {
    partners,        // All partners
    b2cUsers,        // All B2C users
    stats,           // Global stats
    recentLogs,      // System logs
    stylesMetadata   // AI style library
  } = useAdmin({ showToast });
  
  // ...
};

Database Access

-- Master has unrestricted access via RLS policy
CREATE POLICY "master_all_access" ON events
  FOR ALL USING (
    EXISTS (
      SELECT 1 FROM profiles 
      WHERE id = auth.uid() AND is_master = true
    )
  );

Key Features

1

Global Analytics

View platform-wide metrics:
const stats = {
  total_partners: 15,
  total_events: 247,
  total_generations: 12_543,
  total_revenue: '$45,320 USD',
  active_events: 8
};
2

Partner Creation

Onboard new resellers:
// src/hooks/useAdmin.ts:60
const handleCreatePartner = async (partnerData: PartnerFormData) => {
  // 1. Create partner record
  const { data: partner } = await supabaseAdmin
    .from('partners')
    .insert({
      business_name: partnerData.business_name,
      contact_email: partnerData.contact_email,
      contact_name: partnerData.contact_name
    })
    .select()
    .single();
  
  // 2. Create user account
  const { data: user } = await supabaseAdmin.auth.admin.createUser({
    email: partnerData.contact_email,
    password: generateSecurePassword(),
    email_confirm: true
  });
  
  // 3. Link partner to user
  await supabaseAdmin
    .from('profiles')
    .update({ role: 'partner', partner_id: partner.id })
    .eq('id', user.id);
  
  // 4. Send welcome email
  await sendPartnerWelcomeEmail(partnerData.contact_email);
};
3

AI Style Management

Control the style library:
// Toggle style visibility globally
await supabase
  .from('styles_metadata')
  .update({ is_active: false })
  .eq('id', 'pixar_a');

// Set premium flag
await supabase
  .from('styles_metadata')
  .update({ is_premium: true })
  .eq('id', 'renaissance_a');
4

System Monitoring

Real-time log viewing:
// src/components/dashboards/admin/AdminLogsSection.tsx
const { data: logs } = await supabase
  .from('system_logs')
  .select('*')
  .order('created_at', { ascending: false })
  .limit(50);

// Logs include:
// - Generation errors
// - Payment failures
// - Partner actions
// - API rate limits

Level 2: PARTNER (Reseller)

Agencies or photographers who create branded events for clients.

Capabilities

Event Creation

Create unlimited events for clients with custom branding

Credit Management

Purchase credits in bulk, allocate to events

Branding Control

Customize logo, colors, and welcome text per event

Analytics

View stats for ONLY their events (not other partners)

Dashboard Access

// src/components/dashboards/PartnerDashboard.tsx:42
export const PartnerDashboard: React.FC<PartnerDashboardProps> = ({
  user, profile, onBack
}) => {
  const {
    partner,              // Their partner record
    events,               // ONLY their events
    transactions,         // Their credit purchases
    clients,              // Their client list
    generationsData       // Stats for their events
  } = usePartnerDashboard({ profile, showToast });
  
  // Partner CANNOT see:
  // - Other partners' events
  // - Global platform stats
  // - Other partners' clients
};

Database Access

-- Partners can only see their own events
CREATE POLICY "partners_own_events" ON events
  FOR SELECT USING (
    partner_id IN (
      SELECT id FROM partners WHERE user_id = auth.uid()
    )
  );

-- Partners can create events under their account
CREATE POLICY "partners_create_events" ON events
  FOR INSERT WITH CHECK (
    partner_id IN (
      SELECT id FROM partners WHERE user_id = auth.uid()
    )
  );

White-Label Branding

Partners customize the event experience:
// src/hooks/useBranding.ts:85
const handleUpdateBranding = async () => {
  const { error } = await supabase
    .from('partners')
    .update({
      config: {
        logo_url: brandingConfig.logo_url,
        primary_color: brandingConfig.primary_color,
        enabled_styles: brandingConfig.enabled_styles,
        business_tagline: brandingConfig.business_tagline
      }
    })
    .eq('id', partner.id);
  
  // Branding is inherited by all events created by this partner
};
Branding Inheritance: Event-level branding overrides partner-level branding. If an event has no custom logo, it inherits the partner’s logo.

Event Creation Flow

1

Open Create Modal

Partner clicks “Crear Evento” button:
// src/components/dashboards/partner/modals/CreateEventModal.tsx
const [formData, setFormData] = useState({
  event_name: '',
  event_slug: '',
  credits_allocated: 5000,
  selected_styles: [],
  start_date: '',
  end_date: '',
  config: {
    logo_url: partner.config?.logo_url || '',
    primary_color: partner.config?.primary_color || '#7f13ec',
    welcome_text: 'Bienvenidos a nuestro evento'
  }
});
2

Fill Event Details

Partner configures:
  • Event name: “Boda de Juan & María”
  • Slug: boda-juan-maria-2026
  • Credits: 10,000 (100 photos)
  • Dates: March 15-16, 2026
3

Customize Branding

Optional event-specific branding:
  • Upload couple’s logo
  • Choose wedding color (#ff69b4)
  • Set welcome message
4

Select AI Styles

Choose styles available to guests:
selected_styles: [
  'pixar_a',      // Pixar style
  'barbie_a',     // Barbie style
  'magazine_a'    // Magazine cover
]
If no styles are selected, ALL active styles become available (not recommended for focused events).
5

Create Event

// src/hooks/usePartnerDashboard.ts:67
const { data: event, error } = await supabase
  .from('events')
  .insert({
    partner_id: partner.id,
    event_name: formData.event_name,
    event_slug: formData.event_slug,
    credits_allocated: formData.credits_allocated,
    selected_styles: formData.selected_styles,
    config: formData.config,
    start_date: formData.start_date,
    end_date: formData.end_date
  })
  .select()
  .single();

if (error) throw error;
showToast('Evento creado exitosamente');

Credit Wallet

Partners manage credits like a bank account:
// src/components/dashboards/partner/WalletSection.tsx:24
const partnerWallet = {
  total_purchased: 50_000,  // Bought from Master
  allocated: 35_000,        // Assigned to events
  available: 15_000,        // Unallocated balance
  used: 28_450             // Actually consumed
};
Top-Up Events: Partners can add more credits to a running event if it’s running low, preventing guest disappointment.

Level 3: CLIENT (Event Host)

The end client (e.g., bride, quinceañera’s parent) who views their event dashboard.

Capabilities

View Event Stats

See credit usage, photos generated, and live gallery

Download QR Code

Get high-res QR for printing and display

Monitor Live Gallery

Watch photos appear in real-time as guests generate them

Limited Configuration

Can tweak welcome message but NOT branding or credits

Dashboard Access

// src/components/dashboards/ClientDashboard.tsx:1
export const ClientDashboard: React.FC<ClientDashboardProps> = ({
  event, onBack
}) => {
  // Client sees ONLY this event
  // Cannot create new events
  // Cannot access other events
  // Cannot modify credits or styles
  
  const creditsRemaining = event.credits_allocated - event.credits_used;
  const photoCount = event.credits_used / 100; // 100 credits per photo
  
  return (
    <div>
      <h1>{event.event_name}</h1>
      <p>{creditsRemaining} créditos restantes</p>
      <p>{photoCount} fotos generadas</p>
      <QRCodeDownload slug={event.event_slug} />
      <LiveGallery eventId={event.id} />
    </div>
  );
};

Access Control

-- Clients can only view/update their specific event
CREATE POLICY "client_own_event" ON events
  FOR SELECT USING (
    client_email = (SELECT email FROM profiles WHERE id = auth.uid())
  );

-- Clients CANNOT modify credits or branding
CREATE POLICY "client_limited_update" ON events
  FOR UPDATE USING (
    client_email = (SELECT email FROM profiles WHERE id = auth.uid())
  )
  WITH CHECK (
    -- Can only update welcome_text
    config->>'welcome_text' IS DISTINCT FROM OLD.config->>'welcome_text'
    AND credits_allocated = OLD.credits_allocated
    AND selected_styles = OLD.selected_styles
  );
PIN Access: Clients access their dashboard via a simple PIN code (e.g., 1234) rather than full authentication. This keeps it simple for non-technical users.
Real-time feed of guest photos:
// src/components/EventGallery.tsx:14
const LiveGallery = ({ eventId }: { eventId: string }) => {
  const [photos, setPhotos] = useState<any[]>([]);
  
  useEffect(() => {
    // Initial fetch
    fetchPhotos();
    
    // Subscribe to new generations
    const subscription = supabase
      .channel(`event_${eventId}`)
      .on(
        'postgres_changes',
        {
          event: 'INSERT',
          schema: 'public',
          table: 'generations',
          filter: `event_id=eq.${eventId}`
        },
        (payload) => {
          setPhotos(prev => [payload.new, ...prev]);
          // Play celebration sound
          new Audio('/sounds/camera-shutter.mp3').play();
        }
      )
      .subscribe();
    
    return () => subscription.unsubscribe();
  }, [eventId]);
  
  // ...
};

Level 4: GUEST (Attendee)

Event attendees with zero-friction access.

Capabilities

Zero-Friction Access

No registration, no login, no payment required

AI Photo Generation

Select style → Capture photo → Generate → Download

Instant Sharing

WhatsApp share, QR code for later download

Branded Experience

See event’s custom logo, colors, and welcome message

User Experience

// src/components/kiosk/GuestExperience.tsx:23
export const GuestExperience: React.FC<GuestExperienceProps> = ({
  eventConfig, supabase
}) => {
  // No user account needed
  // No credit balance to check
  // No authentication flow
  
  const handleGenerate = async () => {
    const { data } = await supabase.functions.invoke('cabina-vision', {
      body: {
        user_photo: capturedImage,
        model_id: selectedStyle.id,
        event_id: eventConfig.id,
        guest_id: `guest_${Date.now()}`, // Anonymous identifier
        user_id: null // Explicitly null
      }
    });
    
    // Credit deduction happens server-side atomically
  };
};

Access Flow

1

Scan QR Code

Guest scans printed QR at event:
https://app.metalabia.com?event=maria-quince-2026
2

Auto-Load Event

Platform detects ?event= parameter:
// src/App.tsx:283
useEffect(() => {
  const params = new URLSearchParams(window.location.search);
  const eventSlug = params.get('event');
  
  if (eventSlug) {
    fetchEvent(eventSlug);
  }
}, []);
3

Apply Branding

Event’s custom branding loaded:
// src/App.tsx:346
document.documentElement.style.setProperty(
  '--accent-color',
  eventConfig.config.primary_color
);
4

Generate Photo

Guest uses simplified 3-step flow:
  1. Choose style (from event’s selected_styles)
  2. Take selfie
  3. Generate and download
No aspect ratio selection or advanced options - keeps it simple for non-technical users.

Privacy & Limitations

No Account = No History: Guests cannot view past generations. Once they leave, photos are gone unless downloaded.
QR Code for Photo: Each generated photo gets a unique QR code that can be scanned to download later (if enabled by partner).

Permission Matrix

ActionMasterPartnerClientGuest
View all partners
Create partners
View all events
Create events
Edit event branding
View event analytics✅ (own)✅ (own)
Manage AI styles
Generate photos (B2C)
Generate photos (event)
Download QR code
View system logs
Manage B2C users

Code References

ComponentFilePurpose
Master Dashboardsrc/components/dashboards/Admin.tsx:27Full platform control
Partner Dashboardsrc/components/dashboards/PartnerDashboard.tsx:42Reseller interface
Client Dashboardsrc/components/dashboards/ClientDashboard.tsx:1Event host view
Guest Experiencesrc/components/kiosk/GuestExperience.tsx:23Zero-friction generation
RLS PoliciesDatabase schemaAccess control logic

Next Steps

Credit System

Learn how atomic credits work across tiers

Event System

Deep dive into event lifecycle and validation

Business Models

Understand how B2C and B2B2C models interact

Architecture

See the technical implementation

Build docs developers (and LLMs) love