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
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
};
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 );
};
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' );
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
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'
}
});
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
Customize Branding
Optional event-specific branding:
Upload couple’s logo
Choose wedding color (#ff69b4)
Set welcome message
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).
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.
Live Gallery
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
Scan QR Code
Guest scans printed QR at event: https://app.metalabia.com?event=maria-quince-2026
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 );
}
}, []);
Apply Branding
Event’s custom branding loaded: // src/App.tsx:346
document . documentElement . style . setProperty (
'--accent-color' ,
eventConfig . config . primary_color
);
Generate Photo
Guest uses simplified 3-step flow:
Choose style (from event’s selected_styles)
Take selfie
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
Action Master Partner Client Guest 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
Component File Purpose Master Dashboard src/components/dashboards/Admin.tsx:27Full platform control Partner Dashboard src/components/dashboards/PartnerDashboard.tsx:42Reseller interface Client Dashboard src/components/dashboards/ClientDashboard.tsx:1Event host view Guest Experience src/components/kiosk/GuestExperience.tsx:23Zero-friction generation RLS Policies Database schema Access 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