// --- FIREBASE INITIALIZATION ---
const firebaseConfig = typeof __firebase_config !== 'undefined' ? JSON.parse(__firebase_config) : {};
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
const db = getFirestore(app);
const appId = typeof __app_id !== 'undefined' ? __app_id : 'tiktok-affiliates-app';
export default function App() {
const [user, setUser] = useState(null);
const [loggedAffiliateId, setLoggedAffiliateId] = useState(null);
const isManuallyLoggedOut = useRef(false);
const [profile, setProfile] = useState(null);
const [leaderboard, setLeaderboard] = useState([]);
const [allSubmissions, setAllSubmissions] = useState([]);
const [userSubmissions, setUserSubmissions] = useState([]);
const [rewardsData, setRewardsData] = useState({ month: '', items: [] });
const [loading, setLoading] = useState(true);
const [activeTab, setActiveTab] = useState('dashboard');
const [isAdmin, setIsAdmin] = useState(false);
const [showAdminModal, setShowAdminModal] = useState(false);
const [adminPassword, setAdminPassword] = useState('');
const [adminError, setAdminError] = useState('');
useEffect(() => {
const initAuth = async () => {
try {
if (typeof __initial_auth_token !== 'undefined' && __initial_auth_token) {
await signInWithCustomToken(auth, __initial_auth_token);
} else {
await signInAnonymously(auth);
}
} catch (error) {
console.error("Erro na autenticação:", error);
}
};
initAuth();
const unsubscribe = onAuthStateChanged(auth, async (currentUser) => {
setUser(currentUser);
if (currentUser && !loggedAffiliateId && !isManuallyLoggedOut.current) {
try {
const docRef = doc(db, 'artifacts', appId, 'public', 'data', 'affiliates', currentUser.uid);
const docSnap = await getDoc(docRef);
if (docSnap.exists()) {
setLoggedAffiliateId(currentUser.uid);
}
} catch (e) {
console.error(e);
}
}
setLoading(false);
});
return () => unsubscribe();
}, [loggedAffiliateId]);
useEffect(() => {
if (!user) return;
let unsubProfile = () => {};
if (loggedAffiliateId) {
const profileRef = doc(db, 'artifacts', appId, 'public', 'data', 'affiliates', loggedAffiliateId);
unsubProfile = onSnapshot(profileRef, (docSnap) => {
if (docSnap.exists()) {
setProfile(docSnap.data());
} else {
setProfile(null);
}
});
}
const affiliatesRef = collection(db, 'artifacts', appId, 'public', 'data', 'affiliates');
const unsubRanking = onSnapshot(affiliatesRef, (snapshot) => {
const data = snapshot.docs.map(doc => doc.data());
data.sort((a, b) => b.points - a.points);
setLeaderboard(data);
});
const submissionsRef = collection(db, 'artifacts', appId, 'public', 'data', 'submissions');
const unsubSubmissions = onSnapshot(submissionsRef, (snapshot) => {
const data = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));
data.sort((a, b) => b.timestamp - a.timestamp);
setAllSubmissions(data);
if (loggedAffiliateId) {
setUserSubmissions(data.filter(sub => sub.userId === loggedAffiliateId));
}
});
const rewardsRef = doc(db, 'artifacts', appId, 'public', 'data', 'settings', 'rewards');
const unsubRewards = onSnapshot(rewardsRef, (docSnap) => {
if (docSnap.exists()) {
setRewardsData(docSnap.data());
} else {
setRewardsData({ month: '', items: [] });
}
});
return () => {
unsubProfile();
unsubRanking();
unsubSubmissions();
unsubRewards();
};
}, [user, loggedAffiliateId]);
const handleLogin = async (tiktokHandle, password) => {
const handleClean = tiktokHandle.startsWith('@') ? tiktokHandle : `@${tiktokHandle}`;
const snapshot = await getDocs(collection(db, 'artifacts', appId, 'public', 'data', 'affiliates'));
const affiliates = snapshot.docs.map(d => ({ id: d.id, ...d.data() }));
const foundUser = affiliates.find(a => a.tiktokHandle.toLowerCase() === handleClean.toLowerCase() && a.password === password);
if (foundUser) {
isManuallyLoggedOut.current = false;
setLoggedAffiliateId(foundUser.id);
setActiveTab('dashboard');
updateDoc(doc(db, 'artifacts', appId, 'public', 'data', 'affiliates', foundUser.id), {
lastActivity: Date.now()
}).catch(console.error);
} else {
throw new Error('Usuário do TikTok ou senha incorretos.');
}
};
const handleRegister = async (name, tiktokHandle, email, phone, password) => {
const handleClean = tiktokHandle.startsWith('@') ? tiktokHandle : `@${tiktokHandle}`;
const snapshot = await getDocs(collection(db, 'artifacts', appId, 'public', 'data', 'affiliates'));
const affiliates = snapshot.docs.map(d => d.data());
if (affiliates.some(a => a.tiktokHandle.toLowerCase() === handleClean.toLowerCase())) {
throw new Error('Este usuário do TikTok já está cadastrado.');
}
const newId = user ? user.uid : crypto.randomUUID();
await setDoc(doc(db, 'artifacts', appId, 'public', 'data', 'affiliates', newId), {
id: newId,
name,
tiktokHandle: handleClean,
email,
phone,
password,
points: 0,
createdAt: Date.now(),
lastActivity: Date.now()
});
isManuallyLoggedOut.current = false;
setLoggedAffiliateId(newId);
setActiveTab('dashboard');
};
const handleLogout = () => {
isManuallyLoggedOut.current = true;
setLoggedAffiliateId(null);
setProfile(null);
setIsAdmin(false);
};
if (loading) {
return (
Afiliados Sapekitos
);
}
return (
Afiliados Sapekitos
{!loggedAffiliateId && !isAdmin && (
)}
{profile && loggedAffiliateId && (
{profile.name}
{profile.points} pts
)}
{isAdmin && !loggedAffiliateId && (
)}
{!loggedAffiliateId && !isAdmin ? (
) : (
{loggedAffiliateId && (
<>
>
)}
{isAdmin && (
)}
{activeTab === 'dashboard' && profile ? (
) : activeTab === 'ranking' && (profile || isAdmin) ? (
) : isAdmin && activeTab === 'admin' ? (
) : null}
)}
{(loggedAffiliateId || isAdmin) && (
)}
{showAdminModal && (
Insira a senha do marketing para validar os links.
{ setAdminPassword(e.target.value); setAdminError(''); }}
onKeyDown={(e) => {
if (e.key === 'Enter') {
if (adminPassword === 'sapekitos') {
setIsAdmin(true);
setActiveTab('admin');
setShowAdminModal(false);
setAdminPassword('');
} else {
setAdminError('Senha incorreta!');
}
}
}}
className="w-full bg-slate-50 border border-slate-200 rounded-xl px-4 py-3 mb-2 text-slate-800 focus:ring-2 focus:ring-indigo-500 focus:border-transparent outline-none transition-all"
placeholder="Senha do Marketing"
autoFocus
/>
{adminError &&
{adminError}
}
)}
);
}
function AuthScreen({ onLogin, onRegister }) {
const [isLogin, setIsLogin] = useState(true);
const [tiktokHandle, setTiktokHandle] = useState('');
const [password, setPassword] = useState('');
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [phone, setPhone] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState('');
const handleSubmit = async (e) => {
e.preventDefault();
setError('');
setIsLoading(true);
const cleanHandle = tiktokHandle.replace('@', '').trim();
if (!cleanHandle) {
setError('O usuário do TikTok é obrigatório.');
setIsLoading(false);
return;
}
if (!isLogin && (!name || !cleanHandle || !email || !phone || !password)) {
setError('Todos os campos de cadastro são obrigatórios.');
setIsLoading(false);
return;
}
try {
if (isLogin) {
await onLogin(cleanHandle, password);
} else {
await onRegister(name, cleanHandle, email, phone, password);
}
} catch (err) {
setError(err.message);
} finally {
setIsLoading(false);
}
};
return (
{isLogin ? 'Bem-vindo(a) de volta!' : 'Crie sua conta'}
{isLogin ? 'Faça login para gerenciar seus links e ver sua pontuação.' : 'Cadastre-se para participar do nosso ranking de afiliados Sapekitos.'}
);
}
function Dashboard({ profile, loggedAffiliateId, submissions, leaderboard }) {
const [submissionType, setSubmissionType] = useState('video');
const [url, setUrl] = useState('');
const [isSubmitting, setIsSubmitting] = useState(false);
const [successMsg, setSuccessMsg] = useState('');
const [errorMsg, setErrorMsg] = useState('');
const userRankIndex = leaderboard.findIndex(affiliate => affiliate.id === loggedAffiliateId);
const userRank = userRankIndex !== -1 ? `${userRankIndex + 1}º` : '-';
const handleSubmitAction = async (e) => {
e.preventDefault();
if (!url || !url.includes('tiktok.com')) {
setErrorMsg("Por favor, insira um link válido do TikTok.");
setTimeout(() => setErrorMsg(''), 5000);
return;
}
setIsSubmitting(true);
setSuccessMsg('');
setErrorMsg('');
try {
const pointsToAdd = submissionType === 'live' ? 7 : 5;
const subRef = collection(db, 'artifacts', appId, 'public', 'data', 'submissions');
await addDoc(subRef, {
userId: loggedAffiliateId,
userName: profile.name,
tiktokHandle: profile.tiktokHandle,
type: submissionType,
url: url,
timestamp: Date.now(),
status: 'pending',
points: pointsToAdd
});
const userRef = doc(db, 'artifacts', appId, 'public', 'data', 'affiliates', loggedAffiliateId);
updateDoc(userRef, { lastActivity: Date.now() }).catch(console.error);
setUrl('');
setSuccessMsg(`Link enviado para análise! Você receberá os ${pointsToAdd} pontos assim que nossa equipe validar.`);
setTimeout(() => setSuccessMsg(''), 6000);
} catch (error) {
console.error("Erro ao registar ação:", error);
setErrorMsg("Houve um erro ao registar. Tente novamente.");
setTimeout(() => setErrorMsg(''), 5000);
} finally {
setIsSubmitting(false);
}
};
return (
Seus Pontos Aprovados
{profile.points || 0}
PTS
Perfil TikTok
{profile.tiktokHandle}
Ranking Global
{userRank}
{errorMsg && (
)}
{successMsg && (
)}
O link será analisado pela equipe de marketing. Após validado, os pontos serão adicionados ao seu perfil.
Entrar no Grupo WhatsApp
Status dos Links
{submissions.length === 0 ? (
Nenhum envio ainda.
Registre seus links ao lado para começar!
) : (
submissions.map((sub) => {
let statusConfig = { color: 'text-orange-500', bg: 'bg-orange-100', icon: Clock, text: 'Em Análise' };
if (sub.status === 'approved') statusConfig = { color: 'text-lime-600', bg: 'bg-lime-100', icon: CheckCircle, text: 'Aprovado' };
if (sub.status === 'rejected') statusConfig = { color: 'text-red-500', bg: 'bg-red-100', icon: XCircle, text: 'Rejeitado' };
const StatusIcon = statusConfig.icon;
return (
{sub.type === 'video' ? : }
{sub.points} pts
{statusConfig.text}
{sub.status === 'rejected' && sub.rejectionReason && (
Motivo da Rejeição:
{sub.rejectionReason}
)}
);
})
)}
);
}
function AdminPanel({ allSubmissions, leaderboard, rewardsData }) {
const [processingId, setProcessingId] = useState(null);
const [rejectingId, setRejectingId] = useState(null);
const [rejectionReason, setRejectionReason] = useState('');
const [rewardsMonth, setRewardsMonth] = useState(rewardsData?.month || '');
const [rewardsList, setRewardsList] = useState(rewardsData?.items || []);
const [newRewardText, setNewRewardText] = useState('');
const [savingRewards, setSavingRewards] = useState(false);
const [rewardsSuccess, setRewardsSuccess] = useState(false);
useEffect(() => {
setRewardsMonth(rewardsData?.month || '');
setRewardsList(rewardsData?.items || []);
}, [rewardsData]);
const pendingSubs = allSubmissions.filter(s => s.status === 'pending');
const handleValidation = async (submission, isApproved, reason = null) => {
setProcessingId(submission.id);
try {
const subRef = doc(db, 'artifacts', appId, 'public', 'data', 'submissions', submission.id);
const updateData = {
status: isApproved ? 'approved' : 'rejected',
reviewedAt: Date.now()
};
if (!isApproved && reason) {
updateData.rejectionReason = reason;
}
await updateDoc(subRef, updateData);
if (isApproved) {
const userProfile = leaderboard.find(u => u.id === submission.userId);
if (userProfile) {
const userRef = doc(db, 'artifacts', appId, 'public', 'data', 'affiliates', submission.userId);
await updateDoc(userRef, {
points: (userProfile.points || 0) + submission.points
});
}
}
} catch (error) {
console.error("Erro ao validar:", error);
alert("Falha ao processar validação.");
} finally {
setProcessingId(null);
if (!isApproved) {
setRejectingId(null);
setRejectionReason('');
}
}
};
const handleAddReward = () => {
if (newRewardText.trim()) {
setRewardsList([...rewardsList, { id: Date.now().toString(), text: newRewardText }]);
setNewRewardText('');
}
};
const handleRemoveReward = (idToRemove) => {
setRewardsList(rewardsList.filter(r => r.id !== idToRemove));
};
const handleSaveRewards = async () => {
setSavingRewards(true);
setRewardsSuccess(false);
try {
await setDoc(doc(db, 'artifacts', appId, 'public', 'data', 'settings', 'rewards'), {
month: rewardsMonth,
items: rewardsList
});
setRewardsSuccess(true);
setTimeout(() => setRewardsSuccess(false), 3000);
} catch (e) {
console.error("Erro ao salvar premiações", e);
} finally {
setSavingRewards(false);
}
};
return (
Painel do Marketing
Avalie os links e configure as premiações para os afiliados.
Configurar Premiações do Mês
setRewardsMonth(e.target.value)}
placeholder="Ex: Março/2026"
className="w-full bg-slate-50 border border-slate-200 rounded-xl px-4 py-3 text-slate-800 focus:ring-2 focus:ring-pink-400 outline-none transition-all"
/>
{rewardsList.length > 0 && (
Prêmios Atuais
{rewardsList.map((reward) => (
{reward.text}
))}
)}
{rewardsSuccess ? (
Atualizado com sucesso!
) : (
Salve para atualizar o ranking global.
)}
Fila de Análise
{pendingSubs.length} pendentes
{pendingSubs.length === 0 ? (
Oba! Nenhum link pendente de aprovação no momento.
) : (
pendingSubs.map((sub) => (
{sub.userName}
{sub.tiktokHandle}
{sub.type === 'video' ? : }
{sub.type}
Vale {sub.points} pts
{sub.url}
{rejectingId !== sub.id && (
<>
>
)}
{rejectingId === sub.id && (
)}
))
)}
Atividade Recente dos Afiliados
| Afiliado |
Última Atividade |
Status |
{leaderboard.map(affiliate => {
const timestamp = affiliate.lastActivity || affiliate.createdAt;
const daysAgo = timestamp ? Math.floor((Date.now() - timestamp) / (1000 * 60 * 60 * 24)) : null;
let statusConfig = { color: 'text-slate-600 bg-slate-100 border-slate-200', text: 'Sem dados' };
if (daysAgo !== null) {
if (daysAgo <= 15) {
statusConfig = { color: 'text-lime-700 bg-lime-100 border-lime-200', text: daysAgo === 0 ? 'Hoje' : `Há ${daysAgo} dia${daysAgo > 1 ? 's' : ''}` };
} else if (daysAgo <= 30) {
statusConfig = { color: 'text-amber-700 bg-amber-100 border-amber-200', text: `Há ${daysAgo} dias` };
} else {
statusConfig = { color: 'text-red-700 bg-red-100 border-red-200', text: `Há ${daysAgo} dias` };
}
}
const lastDateFormatted = timestamp ? new Date(timestamp).toLocaleDateString('pt-BR') : '-';
return (
|
{affiliate.name}
{affiliate.tiktokHandle}
|
{lastDateFormatted}
|
{statusConfig.text}
|
)
})}
);
}
function Leaderboard({ leaderboard, currentUserId, rewardsData }) {
const top3 = leaderboard.slice(0, 3);
const restOfList = leaderboard.slice(3);
return (
Ranking Sapekitos
Quem produzir mais conteúdo e bater as metas ganhará prêmios exclusivos!
{/* NOVO: EXIBIÇÃO DAS PREMIAÇÕES DO MÊS */}
{rewardsData && rewardsData.items && rewardsData.items.length > 0 && (
Premiações de {rewardsData.month || 'Mês Atual'}
{rewardsData.items.map((reward) => (
))}
)}
{/* Top 3 Podium */}
{top3.length > 0 && (
{/* 2nd Place */}
{top3[1] && (
{top3[1].name.split(' ')[0]}
{top3[1].points} pts
)}
{top3[0] && (
{top3[0].name.split(' ')[0]}
{top3[0].points} pts
)}
{top3[2] && (
{top3[2].name.split(' ')[0]}
{top3[2].points} pts
)}
)}
Tabela Geral
{leaderboard.length} ativos
{leaderboard.length === 0 ? (
Nenhum afiliado registrado ainda.
) : (
leaderboard.map((affiliate, index) => {
const isMe = affiliate.id === currentUserId;
let positionColor = "text-slate-400";
let badgeColor = "bg-slate-50 text-slate-500 border-slate-200";
if (index === 0) { positionColor = "text-yellow-500"; badgeColor = "bg-yellow-100 text-yellow-700 border-yellow-200"; }
else if (index === 1) { positionColor = "text-slate-400"; badgeColor = "bg-slate-100 text-slate-700 border-slate-200"; }
else if (index === 2) { positionColor = "text-amber-500"; badgeColor = "bg-amber-100 text-amber-700 border-amber-200"; }
return (
{index + 1}
{affiliate.name}
{isMe &&
Você}
{affiliate.tiktokHandle}
);
})
)}
);
}