import React, { useState, useMemo } from 'react';
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer, BarChart, Bar, ComposedChart, Area } from 'recharts';
export default function TikTokDashboard() {
// Product & Cost Inputs
const [sellingPrice, setSellingPrice] = useState(120);
const [productCost, setProductCost] = useState(34);
const [shippingCost, setShippingCost] = useState(6);
const [packagingCost, setPackagingCost] = useState(5);
// TikTok Fees
const [platformCommission, setPlatformCommission] = useState(5);
const [paymentProcessing, setPaymentProcessing] = useState(1);
const [affiliateCommission, setAffiliateCommission] = useState(15);
// Inventory
const [initialStock, setInitialStock] = useState(130);
const [initialSamples, setInitialSamples] = useState(40);
// Creator Variables
const [videosPerCreatorPerMonth, setVideosPerCreatorPerMonth] = useState(5);
const [conversionRate, setConversionRate] = useState(0.5);
const [creatorRetentionRate, setCreatorRetentionRate] = useState(80);
// Video Views Distribution (realistic TikTok distribution)
const [flopViews, setFlopViews] = useState(500); // 70% of videos
const [flopPercent, setFlopPercent] = useState(80);
const [averageViews, setAverageViews] = useState(5000); // 20% of videos
const [averagePercent, setAveragePercent] = useState(15);
const [viralViews, setViralViews] = useState(100000); // 10% of videos
// viralPercent is calculated as 100 - flopPercent - averagePercent
// Cash Flow & Reinvestment
const [startingCash, setStartingCash] = useState(0);
const [returnRate, setReturnRate] = useState(3);
const [sampleSeedingPercent, setSampleSeedingPercent] = useState(20);
const [sampleCost, setSampleCost] = useState(40);
const [monthlyExpenses, setMonthlyExpenses] = useState(1000);
// Calculations
const calculations = useMemo(() => {
const sellableStock = initialStock - initialSamples;
const totalFeesPercent = (platformCommission + paymentProcessing + affiliateCommission) / 100;
const totalCostPerUnit = productCost + shippingCost + packagingCost;
const profitPerUnit = sellingPrice * (1 - returnRate/100) * (1 - totalFeesPercent) - totalCostPerUnit;
// Calculate viral percent (remainder)
const viralPercent = Math.max(0, 100 - flopPercent - averagePercent);
// Weighted average views per video
const avgViewsPerVideo = (flopViews * flopPercent + averageViews * averagePercent + viralViews * viralPercent) / 100;
let months = [];
let cumulativeProfit = 0;
let cumulativeUnits = 0;
let cumulativeRevenue = 0;
for (let m = 1; m <= 12; m++) {
const prevMonth = months[m - 2] || {
stockEnd: sellableStock,
cashEnd: startingCash,
unitsOrdered: 0,
netRevenue: 0,
totalFees: 0,
totalActiveCreators: 0,
cashAfterSeeding: startingCash,
cashAfterExpenses: startingCash,
cashAfterRestock: startingCash
};
// --- CREATOR CALCULATIONS (CUMULATIVE) ---
// Month 1: Start with initial samples sent
// Month 2+: Previous creators (with retention) + new creators from seeding
let activeCreatorsFromPast = 0;
let newCreatorsThisMonth = 0;
if (m === 1) {
newCreatorsThisMonth = initialSamples;
} else {
// Existing creators with retention decay
activeCreatorsFromPast = Math.floor(prevMonth.totalActiveCreators * (creatorRetentionRate / 100));
// New creators from sample seeding (using cash available after expenses)
const cashForSeeding = Math.max(0, prevMonth.cashAfterExpenses) * (sampleSeedingPercent / 100);
newCreatorsThisMonth = Math.floor(cashForSeeding / sampleCost);
}
const totalActiveCreators = activeCreatorsFromPast + newCreatorsThisMonth;
const seedingCost = newCreatorsThisMonth * sampleCost;
// --- VIDEO & DEMAND CALCULATIONS ---
const totalVideos = totalActiveCreators * videosPerCreatorPerMonth;
// Calculate views with distribution (add slight randomness per month)
const monthSeed = m * 0.1; // Deterministic "randomness" based on month
const varianceFactor = 0.8 + (Math.sin(monthSeed * 7) * 0.4); // 0.6 to 1.2 variance
const flopVideoCount = Math.floor(totalVideos * (flopPercent / 100));
const avgVideoCount = Math.floor(totalVideos * (averagePercent / 100));
const viralVideoCount = totalVideos - flopVideoCount - avgVideoCount;
const totalViews = Math.round(
(flopVideoCount * flopViews +
avgVideoCount * averageViews +
viralVideoCount * viralViews) * varianceFactor
);
const potentialSales = Math.round(totalViews * (conversionRate / 100));
// --- CASH & STOCK (with settlement delay) ---
// Cash from previous month's sales arrives this month
const cashFromSales = m === 1 ? 0 : (prevMonth.netRevenue - prevMonth.totalFees);
// Stock arriving from previous order (1 month production delay)
const stockArriving = m <= 1 ? 0 : prevMonth.unitsOrdered;
// Available stock at start
const stockStart = m === 1 ? sellableStock : prevMonth.stockEnd + stockArriving;
// Units sold (limited by stock)
const unitsSold = Math.min(stockStart, potentialSales);
const stockEnd = stockStart - unitsSold;
// Cash available (before expenses, seeding and restock)
const cashStart = m === 1 ? startingCash : prevMonth.cashAfterRestock + cashFromSales;
// Deduct monthly personal expenses first
const expensesTaken = Math.min(monthlyExpenses, Math.max(0, cashStart));
const cashAfterExpenses = cashStart - expensesTaken;
// Cash after seeding new creators
const cashAfterSeeding = cashAfterExpenses - seedingCost;
// Order as many units as affordable with remaining cash
const unitsAffordable = Math.floor(Math.max(0, cashAfterSeeding) / productCost);
const unitsOrdered = unitsAffordable > 0 ? unitsAffordable : 0;
const restockCost = unitsOrdered * productCost;
const cashAfterRestock = cashAfterSeeding - restockCost;
// --- REVENUE & COSTS ---
const grossRevenue = unitsSold * sellingPrice;
const returns = grossRevenue * (returnRate / 100);
const netRevenue = grossRevenue - returns;
const platformFee = netRevenue * (platformCommission / 100);
const paymentFee = netRevenue * (paymentProcessing / 100);
const affiliateFee = netRevenue * (affiliateCommission / 100);
const totalFees = platformFee + paymentFee + affiliateFee;
const cogs = unitsSold * productCost;
const shippingTotal = unitsSold * (shippingCost + packagingCost);
const totalCosts = cogs + shippingTotal;
// --- PROFIT ---
const monthlyProfit = netRevenue - totalFees - totalCosts;
const margin = netRevenue > 0 ? (monthlyProfit / netRevenue) * 100 : 0;
// --- CUMULATIVE ---
cumulativeProfit += monthlyProfit;
cumulativeUnits += unitsSold;
cumulativeRevenue += grossRevenue;
months.push({
month: `M${m}`,
monthNum: m,
// Creators
activeCreatorsFromPast,
newCreatorsThisMonth,
totalActiveCreators,
seedingCost,
// Content
totalVideos,
flopVideoCount,
avgVideoCount,
viralVideoCount,
totalViews,
avgViewsThisMonth: totalVideos > 0 ? Math.round(totalViews / totalVideos) : 0,
potentialSales,
// Target views to sell available stock
targetViews: stockStart > 0 ? Math.round(stockStart / (conversionRate / 100)) : 0,
// Stock
stockStart,
stockArriving,
unitsSold,
stockEnd,
unitsOrdered,
restockCost,
// Cash
cashStart,
expensesTaken,
cashAfterExpenses,
cashAfterSeeding,
cashAfterRestock,
// Revenue
grossRevenue,
netRevenue,
totalFees,
totalCosts,
monthlyProfit,
margin,
// Cumulative
cumulativeProfit,
cumulativeUnits,
cumulativeRevenue
});
}
return {
months,
sellableStock,
profitPerUnit,
totalProfit: cumulativeProfit,
totalUnits: cumulativeUnits,
totalRevenue: cumulativeRevenue,
totalCreatorsRecruited: months.reduce((sum, m) => sum + m.newCreatorsThisMonth, 0),
totalExpensesTaken: months.reduce((sum, m) => sum + m.expensesTaken, 0),
viralPercent,
avgViewsPerVideo
};
}, [sellingPrice, productCost, shippingCost, packagingCost, platformCommission,
paymentProcessing, affiliateCommission, initialStock, initialSamples,
videosPerCreatorPerMonth, flopViews, flopPercent, averageViews, averagePercent,
viralViews, conversionRate, creatorRetentionRate,
startingCash, returnRate, sampleSeedingPercent, sampleCost, monthlyExpenses]);
const formatCurrency = (val) => `$${val.toLocaleString('en-US', {maximumFractionDigits: 0})}`;
const formatNumber = (val) => val.toLocaleString('en-US', {maximumFractionDigits: 0});
const Slider = ({ label, value, setValue, min, max, step = 1, format = 'number', suffix = '', hint = '' }) => (
{label}
{format === 'currency' ? `$${value}` : value}{suffix}
setValue(parseFloat(e.target.value))}
className="w-full h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer accent-blue-500"
/>
{hint &&
{hint}
}
);
return (
TikTok Shop Profit Dashboard
Cumulative Creator Model with Reinvestment
{/* Summary Cards */}
12-Month Profit
{formatCurrency(calculations.totalProfit)}
Total Revenue
{formatCurrency(calculations.totalRevenue)}
Units Sold
{formatNumber(calculations.totalUnits)}
Total Creators
{formatNumber(calculations.totalCreatorsRecruited)}
Your Take-Home
{formatCurrency(calculations.totalExpensesTaken)}
Profit/Unit
{formatCurrency(calculations.profitPerUnit)}
{/* Controls Column 1 */}
{/* Product */}
π° Product & Costs
{/* Fees */}
π TikTok Fees
{/* Inventory */}
π¦ Initial Inventory
Sellable Stock: {calculations.sellableStock} units
{/* Controls Column 2 */}
{/* Creators */}
π¬ Creator Performance
{/* Video Views Distribution */}
π Video Views Distribution
Mimics real TikTok: most flop, some average, few go viral
π Flops ({flopPercent}%)
π Average ({averagePercent}%)
π Viral ({calculations.viralPercent}%)
Weighted Avg Views/Video:
{formatNumber(calculations.avgViewsPerVideo)}
{/* Reinvestment */}
π± Cash Allocation
{/* Monthly Demand Calc */}
π Month 12 Projection
Active Creators: {calculations.months[11]?.totalActiveCreators || 0}
Total Videos: {formatNumber(calculations.months[11]?.totalVideos || 0)}
π{calculations.months[11]?.flopVideoCount || 0}
π{calculations.months[11]?.avgVideoCount || 0}
π{calculations.months[11]?.viralVideoCount || 0}
Total Views: {formatNumber(calculations.months[11]?.totalViews || 0)}
Avg Views/Video: {formatNumber(calculations.months[11]?.avgViewsThisMonth || 0)}
Demand: {formatNumber(calculations.months[11]?.potentialSales || 0)} units
{/* Charts */}
{/* Creator Growth Chart */}
{/* Profit Chart */}
Monthly Profit
`$${(v/1000).toFixed(0)}k`} />
[formatCurrency(value), 'Profit']}
/>
{/* Units Chart */}
Demand vs Stock vs Sold
{/* Monthly Table */}
Monthly Breakdown
| Month |
Creators |
Videos |
πViral |
Stock |
Views Needed |
Actual Views |
Sold |
Profit |
Your Take |
Cash End |
{calculations.months.map((m, i) => (
| {m.month} |
{m.totalActiveCreators} |
{formatNumber(m.totalVideos)} |
{m.viralVideoCount} |
{formatNumber(m.stockStart)} |
= m.targetViews ? 'text-green-400' : 'text-red-400'}`}>
{formatNumber(m.targetViews)}
|
{formatNumber(m.totalViews)} |
{formatNumber(m.unitsSold)} |
0 ? 'text-green-400' : 'text-red-400'}`}>
{formatCurrency(m.monthlyProfit)}
|
{formatCurrency(m.expensesTaken)} |
{formatCurrency(m.cashAfterRestock)} |
))}
{/* How it works */}
How the model works:
- Month 1: You send {initialSamples} samples β {initialSamples} creators start posting
- Retention: Each month, {creatorRetentionRate}% of existing creators continue posting
- Video Distribution: {flopPercent}% flop ({formatNumber(flopViews)} views) β’ {averagePercent}% average ({formatNumber(averageViews)} views) β’ {calculations.viralPercent}% go viral ({formatNumber(viralViews)} views)
- Cash Priority: Your ${formatNumber(monthlyExpenses)}/month β seeding β restocking
- Cash delay: Sales money arrives next month (settlement), stock takes 1 month to produce
);
}