| name | mixmi-user-flows |
| description | Step-by-step user journeys through the mixmi platform including onboarding, upload, remix creation, purchasing, and discovery |
| metadata | [object Object] |
mixmi User Flows
Complete step-by-step journeys showing how users interact with the mixmi platform.
Table of Contents
- Flow 1: Alpha User Onboarding
- Flow 2: Quick Upload (Minimal)
- Flow 3: Advanced Upload (Full Metadata)
- Flow 4: Creating a Remix (Planned)
- Flow 5: Purchasing Content
- Flow 6: Discovery via Globe
- Flow 7: Discovery via Search
- Flow 8: Browsing Creator Stores
- Flow 9: Exploring Artist Profiles
- Flow 10: Editing Your Profile
Flow 1: Alpha User Onboarding
Goal: Authenticate and gain upload access Duration: 1-2 minutes Authentication Methods: Stacks Wallet OR Alpha Invite Code
Path A: Stacks Wallet Authentication (Preferred)
Step 1: Navigate to Platform
URL: https://mixmi-alpha.vercel.app/ (or localhost)
Page: Globe homepage (app/page.tsx)
State: User not authenticated
UI:
- Header shows "Sign In" button (top-right)
- Globe visible with existing content
- No upload button visible
Step 2: Click "Sign In"
Action: User clicks "Sign In" in header
Component: Header.tsx → opens SignInModal.tsx
Modal Displays:
- "Welcome to mixmi Alpha" header
- Two options:
- "Connect Wallet" button (cyan, primary)
- "Use Alpha Code" link (secondary, text link below)
Step 3: Click "Connect Wallet"
Action: User clicks "Connect Wallet"
Library: @stacks/connect (Stacks blockchain wallet integration)
What Happens:
import { showConnect } from '@stacks/connect';
showConnect({
appDetails: {
name: 'mixmi Alpha',
icon: window.location.origin + '/logo.png'
},
onFinish: (data) => {
// Wallet connection successful
const walletAddress = data.userSession.loadUserData().profile.stxAddress.mainnet;
// Store in AuthContext
},
onCancel: () => {
// User closed wallet popup
}
});
Wallet Popup Appears:
- Shows available wallets (Hiro, Xverse, Leather, etc.)
- User selects their wallet
- Wallet asks to approve connection
- User signs authentication message
Step 4: Alpha Whitelist Verification
Backend Call:
const response = await fetch('/api/auth/alpha-check', {
method: 'POST',
body: JSON.stringify({ walletAddress })
});
const { isApproved, alphaCode } = await response.json();
Database Query:
SELECT alpha_code, is_active
FROM alpha_users
WHERE wallet_address = 'SP...'
AND is_active = true;
Two Outcomes:
✅ If Approved:
AuthContextsetsisAuthenticated = truewalletAddressstored in context- Modal closes automatically
- Header now shows:
- "Upload" button appears
- Wallet address (truncated): "SP1ABC...XYZ"
- Cart icon
❌ If Not Approved:
- Error message: "Wallet not approved for alpha access"
- Option to request access (future)
- User must use alpha code instead
Path B: Alpha Code Authentication (Fallback)
Step 1-2: Same as Path A
Step 3: Click "Use Alpha Code"
UI Changes:
- "Connect Wallet" button dims/hides
- Text input appears: "Enter your alpha invite code"
- Placeholder: "mixmi-ABC123"
- "Submit" button
Step 4: Enter Alpha Code
User Types: mixmi-ABC123
Action: Clicks "Submit"
Validation (Frontend):
const isAlphaCode = (code) => {
return /^mixmi-[A-Z0-9]{6}$/.test(code);
};
Backend Conversion:
// User entered: mixmi-ABC123
// Backend resolves to actual wallet
const response = await fetch('/api/auth/resolve-wallet', {
method: 'POST',
body: JSON.stringify({ authIdentity: 'mixmi-ABC123' })
});
const { walletAddress } = await response.json();
// Returns: SP1ABC...XYZ
Why This Works:
- UI never shows "wallet address" (prevents security scanner warnings)
- Backend transparently converts alpha code → wallet
- Blockchain operations use real wallet address
- User-friendly authentication
Step 5: Authenticated State
AuthContext State:
{
isAuthenticated: true,
walletAddress: 'SP1ABC...',
alphaCode: 'mixmi-ABC123', // For display
authMethod: 'alpha_code' // or 'wallet'
}
UI Changes:
- Modal closes
- Header shows upload button
- User can now upload content
First Upload Incentive
After Authentication:
- Toast notification: "You're in! Ready to upload your first track?"
- Welcome page: "Sign In and Upload" button now says "Upload Your First Track"
Flow 2: Quick Upload (Minimal)
Goal: Upload a loop or song in ~60 seconds Prerequisites: User authenticated Duration: 1-2 minutes
Step 1: Open Upload Modal
Triggers:
- Click "Upload" in header
- Click "Sign In and Upload" on Welcome page (if authenticated)
- Click "+" in own store
Component: IPTrackModal opens
Mode: Quick Upload (default, toggle enabled)
Step 2: Select Content Type
UI: 5 large buttons with icons
┌─────────┬─────────┬─────────┬─────────┬─────────┐
│ 🎵 Song │ 🔁 Loop │📦 Pack │ 💿 EP │ 🎚️ Mix │
└─────────┴─────────┴─────────┴─────────┴─────────┘
User Clicks: "🔁 Loop"
State Change:
setFormData({ ...formData, content_type: 'loop' });
UI Updates:
- BPM field becomes required (red asterisk)
- Loop category dropdown appears
Step 3: Upload Audio File
UI: File picker or drag zone (drag-and-drop for audio not yet implemented)
User Actions:
- Clicks "Choose Audio File"
- Selects
trap-beat-140.wav(8MB)
File Validation:
// Check format
const validFormats = ['audio/wav', 'audio/mpeg', 'audio/mp4', 'audio/flac'];
if (!validFormats.includes(file.type)) {
throw new Error('Invalid audio format');
}
// Check size
if (file.size > 10 * 1024 * 1024) {
throw new Error('File too large (max 10MB)');
}
Auto-Detection Starts:
// BPM detection
setIsDetectingBPM(true);
const detectedBPM = await detectBPM(file);
// Result: 140 BPM
// Duration detection
const duration = await detectDuration(file);
// Result: 32 seconds
UI Updates:
- Progress bar shows during detection
- "BPM: 140 (auto-detected)" appears
- User MUST verify/override this value
Step 4: Fill Basic Metadata
Required Fields (Quick Mode):
Title: [Trap Beat 140 ] *
Artist: [DJ Example ] *
BPM: [140 ] * (pre-filled, editable)
Loop Category: [▼ Beats ] * (dropdown)
Location: [Los Angeles, CA ] * (autocomplete)
BPM Verification:
⚠️ User clicks BPM field
Modal: "Auto-detected 140 BPM. Is this correct?"
[Yes, 140 is correct] [No, let me change it]
If user changes to 142:
✓ BPM updated to 142 (whole number only)
Location Autocomplete:
User types: "Los"
Suggestions appear:
┌──────────────────────────┐
│ 🏙️ Los Angeles, CA, USA │
│ 🏙️ Los Alamos, NM, USA │
│ 🏙️ Louisiana, USA │
│ 🏔️ Louisiana Nation │
└──────────────────────────┘
User selects: Los Angeles, CA, USA
Coordinates Stored:
{
location_name: "Los Angeles, CA, USA",
location_lat: 34.052234,
location_lng: -118.243685
}
Step 5: Upload Cover Image
UI: File picker or drag zone
Auto-Generation Option:
┌────────────────────────────┐
│ 📷 Upload Cover Image │
│ │
│ [Choose File] │
│ or │
│ [🎨 Auto-Generate] │
└────────────────────────────┘
If Auto-Generate:
// Creates visual from audio waveform
const generateCover = async (audioFile) => {
const waveformCanvas = await createWaveformCanvas(audioFile);
const blob = await canvasToBlob(waveformCanvas);
return blob;
};
Manual Upload Validation:
// Check image dimensions
const img = new Image();
img.src = URL.createObjectURL(file);
if (img.width < 500 || img.height < 500) {
alert('Image too small (min 500x500)');
}
if (img.width !== img.height) {
alert('Image should be square');
}
Step 6: Review and Submit
Pre-Submit Summary:
┌──────────────────────────────┐
│ Ready to Upload? │
├──────────────────────────────┤
│ Type: Loop │
│ Title: Trap Beat 140 │
│ Artist: DJ Example │
│ BPM: 140 │
│ Location: Los Angeles, CA │
│ Files: ✅ Audio ✅ Cover │
│ │
│ [← Back] [Upload Track →] │
└──────────────────────────────┘
Upload Process:
// 1. Upload audio to Supabase Storage
const audioPath = `${walletAddress}/${Date.now()}_audio.wav`;
await supabase.storage
.from('user-content')
.upload(audioPath, audioFile);
// 2. Upload cover image
const imagePath = `${walletAddress}/${Date.now()}_cover.jpg`;
await supabase.storage
.from('user-content')
.upload(imagePath, imageFile);
// 3. Create database record
const { data: track } = await supabase
.from('ip_tracks')
.insert({
title: formData.title,
artist: formData.artist,
content_type: 'loop',
bpm: formData.bpm,
audio_url: audioPath,
cover_image_url: imagePath,
location_name: formData.location_name,
location_lat: formData.location_lat,
location_lng: formData.location_lng,
primary_uploader_wallet: walletAddress,
created_at: new Date().toISOString()
})
.select()
.single();
// 4. Success!
return track;
Step 7: Success State
Modal Updates:
┌──────────────────────────────┐
│ ✅ Upload Complete! │
├──────────────────────────────┤
│ │
│ Your track "Trap Beat 140" │
│ is now live on mixmi! │
│ │
│ [View Track] [Upload Another]│
└──────────────────────────────┘
Actions:
- "View Track" → Opens TrackDetailsModal
- "Upload Another" → Resets form
- Close modal → Returns to previous page
Flow 3: Advanced Upload (Full Metadata)
Goal: Upload with complete IP attribution Prerequisites: User authenticated Duration: 3-5 minutes
Toggle to Advanced Mode
Location: Top of IPTrackModal
Quick Upload ○━━━━━● Advanced Upload
↑ User slides toggle
What Changes:
- Additional metadata fields appear
- IP splitting interface shows
- Referrer section enabled
- Full credits section available
Additional Fields in Advanced Mode
IP Splitting Configuration
UI:
┌──────────────────────────────┐
│ IP Splits │
├──────────────────────────────┤
│ Producer: [You ] [60%] │
│ + Add Collaborator │
│ │
│ Platform Fee: 15% │
│ Available: 25% │
│ │
│ [+ Add Split Recipient] │
└──────────────────────────────┘
Adding Collaborator:
// User clicks "+ Add Collaborator"
// New row appears:
{
role: "Vocalist", // Dropdown
wallet: "SP2ABC...", // Text input
percentage: 25 // Number input
}
// Validation
if (totalPercentage > 85) {
error: "Total cannot exceed 85% (15% platform fee)"
}
Credits Section
Fields:
Credits (Optional)
├─ Vocalist: [Jane Doe ]
├─ Mixing Engineer: [John Mix ]
├─ Mastering: [Master Studios ]
└─ + Add Credit Field
Referrer Attribution
Field:
Referred By (Optional)
[SP2DEF...] or [mixmi-XYZ789]
Help: Who told you about mixmi?
Validation:
// Check if valid wallet or alpha code
const isValidReferrer = (input) => {
return isWalletAddress(input) || isAlphaCode(input);
};
Complete Upload Flow
All steps from Quick Upload, plus:
- IP splits configuration
- Credits attribution
- Referrer tracking
- Extended metadata
Flow 4: Creating a Remix (Planned)
Goal: Create and save a remix using the mixer Prerequisites: Authenticated, tracks loaded in mixer Status: Coming in Generation 1
Planned Implementation
Step 1: Load Tracks to Mixer
- Drag loops from Crate to Deck A/B
- Or click "Load to Mixer" from track card
Step 2: Create Mix
- Adjust BPM, apply FX
- Record live mixing session
- Mix auto-saves as WebM/Opus
Step 3: Save Recording
Planned Modal:
┌──────────────────────────────┐
│ Save Your Remix │
├──────────────────────────────┤
│ Title: [Summer Mix 2025 ] │
│ Duration: 3:42 │
│ │
│ Source Tracks: │
│ • Loop 1 by Artist A (30%) │
│ • Loop 2 by Artist B (30%) │
│ Platform Fee: 15% │
│ Your Share: 25% │
│ │
│ [Cancel] [Save & Upload] │
└──────────────────────────────┘
Step 4: Auto-Calculate IP Splits
// Automatic attribution based on source loops
const calculateRemixSplits = (sourceTracks) => {
const remixerShare = 25; // Fixed for remixer
const platformFee = 15; // Fixed platform fee
const sourceShare = 60; // Divided among sources
const perSource = sourceShare / sourceTracks.length;
return sourceTracks.map(track => ({
recipient: track.primary_uploader_wallet,
percentage: perSource,
role: 'Original Creator'
}));
};
Flow 5: Purchasing Content
Goal: Purchase and download a track Prerequisites: None (can purchase without auth) Duration: 30 seconds - 2 minutes
Step 1: Find Track to Purchase
Entry Points:
- Globe page card hover → price badge
- Store page → track cards
- Search results → price badges
- Track details modal → purchase button
Step 2: Click Price Badge
UI Example:
Track card hover state:
┌─────────────┐
│ Cover │
│ Image │
│ [$2.99 STX] │ ← Click this
└─────────────┘
Step 3: Add to Cart
Action: Click price badge Result: Track added to cart
Cart Updates:
- Cart icon shows item count: 🛒 (1)
- Toast notification: "Added to cart!"
- Price badge changes to "✓ In Cart"
Step 4: Open Cart
Click: Cart icon in header Modal: Shopping cart opens
┌──────────────────────────────┐
│ Shopping Cart [×] │
├──────────────────────────────┤
│ │
│ 1. Trap Beat 140 │
│ by DJ Example │
│ Type: Loop │
│ $2.99 STX [🗑️] │
│ │
│ ───────────────────────── │
│ Total: $2.99 STX │
│ │
│ [Continue Shopping] │
│ [Proceed to Checkout →] │
└──────────────────────────────┘
Step 5: Checkout Process
If Not Authenticated:
┌──────────────────────────────┐
│ Sign In to Purchase │
├──────────────────────────────┤
│ Connect your wallet to │
│ complete this purchase │
│ │
│ [Connect Wallet] │
│ [Use Alpha Code] │
└──────────────────────────────┘
If Authenticated:
┌──────────────────────────────┐
│ Confirm Purchase │
├──────────────────────────────┤
│ Track: Trap Beat 140 │
│ Price: $2.99 STX │
│ From: Your Wallet │
│ To: Creator Wallet │
│ │
│ IP Splits: │
│ • Creator: 60% ($1.79) │
│ • Platform: 15% ($0.45) │
│ • Referrer: 10% ($0.30) │
│ • Other: 15% ($0.45) │
│ │
│ [Cancel] [Confirm Purchase] │
└──────────────────────────────┘
Step 6: Blockchain Transaction
Process:
// 1. Initialize Stacks transaction
const transaction = {
contractAddress: 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM',
contractName: 'mixmi-payments',
functionName: 'purchase-track',
functionArgs: [
trackId,
priceInMicroSTX,
ipSplits
]
};
// 2. User approves in wallet
await openContractCall(transaction);
// 3. Wait for confirmation
// Shows loading state: "Processing payment..."
// 4. Transaction confirmed
Step 7: Download Access
Success Modal:
┌──────────────────────────────┐
│ ✅ Purchase Complete! │
├──────────────────────────────┤
│ │
│ Thank you for supporting │
│ DJ Example! │
│ │
│ [Download Track] │
│ [View in Library] │
│ [Continue Shopping] │
└──────────────────────────────┘
Download Process:
// Generate temporary download URL
const { data: downloadUrl } = await supabase
.from('purchases')
.insert({
buyer_wallet: walletAddress,
track_id: track.id,
transaction_hash: txHash,
purchased_at: new Date()
})
.select('download_token')
.single();
// Trigger download
window.open(`/api/download/${downloadUrl.download_token}`);
Flow 6: Discovery via Globe
Goal: Discover new music through map interface Prerequisites: None Duration: Ongoing browsing
Globe Interaction
Default View:
- 3D rotating globe
- Glowing dots for track locations
- Auto-rotation at 0.001 radians/frame
Hover on Location Dot
Visual Feedback:
- Dot grows (1.5x scale)
- Glow intensifies
- Tooltip appears with city name
Click on Location
Action: Click glowing dot Result: Track cards appear
┌─────────────────────────────┐
│ Los Angeles, CA │
│ 12 tracks │
├─────────────────────────────┤
│ [Track] [Track] [Track] │
│ [Track] [Track] [Track] │
│ Load More... │
└─────────────────────────────┘
Track Card Interactions
Hover State:
- Card scales to 1.05x
- Overlay with track details
- Action buttons appear
Available Actions:
- Play preview (▶️)
- Add to cart ($)
- View details (ℹ️)
- Load to mixer (🎛️) - if loop
Flow 7: Discovery via Search
Goal: Find specific tracks or artists Prerequisites: None Duration: 10-30 seconds
Access Search
Trigger: Click search icon in header Modal: Search interface opens
Enter Search Query
Search Input:
┌──────────────────────────────┐
│ 🔍 Search mixmi │
├──────────────────────────────┤
│ [trap beats 140 bpm_____] │
│ │
│ Filter by: │
│ ○ All ● Tracks ○ Artists │
│ ○ Loops ○ Songs │
└──────────────────────────────┘
Real-time Results
As User Types:
// Debounced search (300ms)
const debouncedSearch = debounce(async (query) => {
const { data: results } = await supabase
.from('ip_tracks')
.select('*')
.or(`title.ilike.%${query}%,artist.ilike.%${query}%`)
.limit(20);
setSearchResults(results);
}, 300);
Results Display
Search Results:
┌──────────────────────────────┐
│ Results for "trap beats" │
├──────────────────────────────┤
│ │
│ Tracks (8) │
│ ├─ Trap Beat 140 │
│ │ by DJ Example • Loop │
│ ├─ Dark Trap 150 │
│ │ by Producer X • Loop │
│ └─ ... │
│ │
│ Artists (3) │
│ ├─ Trap Lord │
│ ├─ Trap Queen │
│ └─ ... │
└──────────────────────────────┘
Select Result
Click Action:
- Track → Opens TrackDetailsModal
- Artist → Navigate to artist profile
Flow 8: Browsing Creator Stores
Goal: Explore a creator's catalog Prerequisites: None Duration: 2-5 minutes browsing
Navigate to Store
Entry Points:
- Click artist name on track card
- Click "View Store" in profile
- URL:
/store/[identifier]
Store Layout
Header Section:
┌──────────────────────────────┐
│ DJ Example's Store │
│ 12 tracks • Member since Oct│
│ │
│ [Follow] [Share] [Tip] │
└──────────────────────────────┘
Track Grid:
Filters: [All] [Loops] [Songs] [Packs]
Sort: [Newest ▼]
┌────┬────┬────┬────┐
│Loop│Loop│Song│Pack│
├────┼────┼────┼────┤
│Loop│EP │Loop│Song│
├────┼────┼────┼────┤
│... │... │... │... │
└────┴────┴────┴────┘
Filter and Sort
Filter Options:
const filters = {
all: null,
loops: "content_type = 'loop'",
songs: "content_type = 'full_song'",
packs: "content_type IN ('loop_pack', 'ep')"
};
Sort Options:
- Newest First (default)
- Oldest First
- Price: Low to High
- Price: High to Low
- Most Downloaded (future)
Flow 9: Exploring Artist Profiles
Goal: Learn about an artist Prerequisites: None Duration: 1-2 minutes
Navigate to Profile
Entry Points:
- Click artist avatar
- Click artist name (when underlined)
- URL:
/profile/[identifier]
Profile Sections
Header:
┌──────────────────────────────┐
│ [Avatar] │
│ DJ Example │
│ "Making beats daily" │
│ │
│ 📍 Los Angeles, CA │
│ 🎵 12 tracks │
│ 👥 234 followers (future) │
└──────────────────────────────┘
Bio Section:
About
─────
Producer and DJ from LA, specializing
in trap and hip-hop beats. Available
for custom work and collaborations.
[Instagram] [SoundCloud] [Twitter]
Recent Tracks:
Recent Uploads
──────────────
[Track] [Track] [Track] [Track]
View All →
Social Actions (Future)
Planned Features:
- Follow artist
- Send message
- Book for collaboration
- Tip directly (STX/BTC)
Flow 10: Editing Your Profile
Goal: Update profile information Prerequisites: Authenticated Duration: 2-3 minutes
Navigate to Own Profile
Access:
- Click avatar in header → "My Profile"
- URL:
/profile/meredirects to/profile/[wallet]
Edit Profile Button
Location: Top-right of profile when viewing own Button: "Edit Profile" (pencil icon)
Profile Edit Modal
Sections:
Basic Information
Display Name*
[DJ Example_______________]
Tagline (40 chars)
[Making beats daily_______]
Bio (350 chars)
[Producer and DJ from LA,
specializing in trap and
hip-hop beats...]
[Save Changes]
Avatar Upload
Profile Image
┌──────────┐
│ Current │ [Change Photo]
│ Avatar │
└──────────┘
Drag & drop or choose file
Max 5MB • Square recommended
Social Links (Future)
Social Links
├─ Instagram: [@djexample____]
├─ SoundCloud: [/djexample___]
├─ Twitter: [@djexample_____]
└─ + Add Link
Save Changes
Process:
// Update profile in database
await supabase
.from('user_profiles')
.upsert({
wallet_address: walletAddress,
display_name: formData.name,
tagline: formData.tagline,
bio: formData.bio,
avatar_url: uploadedImageUrl,
social_links: formData.socialLinks,
updated_at: new Date()
});
// Update local ProfileContext
updateProfile(newProfileData);
// Close modal
onClose();
Success:
- Changes reflect immediately
- No page refresh needed
- Toast: "Profile updated!"
Related Skills
- mixmi-component-library - UI components used in flows
- mixmi-design-patterns - Visual patterns for extending flows
- mixmi-payment-flow - Detailed payment implementation
- mixmi-database-schema - Data structures behind flows