diff --git a/firebase-blueprint.json b/firebase-blueprint.json index 8d04601..ccd13bf 100644 --- a/firebase-blueprint.json +++ b/firebase-blueprint.json @@ -53,6 +53,16 @@ } }, "required": ["userId", "userName", "text", "timestamp"] + }, + "AppSettings": { + "title": "App Settings", + "description": "Global application settings.", + "type": "object", + "properties": { + "maintenanceMode": { "type": "boolean" }, + "maintenanceMessage": { "type": "string" } + }, + "required": ["maintenanceMode"] } }, "firestore": { @@ -67,6 +77,11 @@ "testimonials/{testimonialId}": { "schema": "Testimonial", "description": "Public user testimonials." + }, + "settings/{settingId}": { + "schema": "AppSettings", + "description": "Global application settings." } } } + diff --git a/firestore.rules b/firestore.rules index 8ef700e..9a1aa63 100644 --- a/firestore.rules +++ b/firestore.rules @@ -58,7 +58,7 @@ service cloud.firestore { && data.name is string && data.name.size() <= 100 && (data.isPremium is bool || data.isPremium == null) && (data.isAdmin is bool || data.isAdmin == null) - && data.plan in ['basic', 'pro', 'elite'] + && data.plan in ['basic', 'pro', 'elite', 'lifetime'] && (data.analysisLimit is int || data.analysisLimit == null) && (data.isApproved is bool || data.isApproved == null); } @@ -120,5 +120,11 @@ service cloud.firestore { allow create: if isSignedIn() && isValidTestimonial(incoming()); allow update, delete: if isDbAdmin(); } + // --- Settings Collection --- + match /settings/{settingId} { + allow read: if true; + allow update, create, delete: if isDbAdmin(); + } } } + diff --git a/src/App.tsx b/src/App.tsx index fbe04a1..1838985 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -13,10 +13,11 @@ import { AdminDashboard } from './components/AdminDashboard'; import { LandingPage } from './components/LandingPage'; import { ProfileView } from './components/ProfileView'; import { NotificationManager } from './components/NotificationManager'; +import { MaintenancePage } from './components/MaintenancePage'; import { TrendingUp, ShieldAlert, Ghost, Mail, Lock, UserPlus, LogIn, Loader2, ArrowLeft, User as UserIcon, Eye, EyeOff } from 'lucide-react'; import { motion, AnimatePresence } from 'motion/react'; import { onAuthStateChanged, signInWithEmailAndPassword, createUserWithEmailAndPassword, updateProfile, signOut, sendEmailVerification, sendPasswordResetEmail, User, setPersistence, browserLocalPersistence, browserSessionPersistence } from 'firebase/auth'; -import { doc, setDoc, getDoc, updateDoc } from 'firebase/firestore'; +import { doc, setDoc, getDoc, updateDoc, onSnapshot } from 'firebase/firestore'; import { auth, db, handleFirestoreError, OperationType } from './lib/firebase'; export default function App() { @@ -24,6 +25,8 @@ export default function App() { const [userData, setUserData] = useState(null); const [loading, setLoading] = useState(true); const [activeTab, setActiveTab] = useState('scan'); + const [maintenanceMode, setMaintenanceMode] = useState(false); + const [maintenanceMessage, setMaintenanceMessage] = useState(''); // Auth & View State const [isSecretAdminRoute, setIsSecretAdminRoute] = useState(() => window.location.pathname === '/fxbros-admin' || window.location.hash === '#/fxbros-admin'); @@ -45,6 +48,21 @@ export default function App() { const [unauthView, setUnauthView] = useState<'hero' | 'plans'>('hero'); + useEffect(() => { + // Listen for settings changes + const settingsRef = doc(db, 'settings', 'app'); + const unsubSettings = onSnapshot(settingsRef, (docSnap) => { + if (docSnap.exists()) { + setMaintenanceMode(docSnap.data().maintenanceMode || false); + setMaintenanceMessage(docSnap.data().maintenanceMessage || ''); + } + }, (error) => { + console.warn("Failed to listen to settings:", error); + }); + + return () => unsubSettings(); + }, []); + useEffect(() => { const unsubscribe = onAuthStateChanged(auth, async (currentUser) => { if (currentUser && currentUser.emailVerified) { @@ -235,6 +253,11 @@ export default function App() { ); } + // Se estiver em manutenção e o usuário não for admin, mostra a página de manutenção + if (maintenanceMode && !isAdmin) { + return ; + } + if (!user) { if (!showAuth) { if (unauthView === 'hero') { diff --git a/src/components/AdminDashboard.tsx b/src/components/AdminDashboard.tsx index 7c6efbe..8253ebe 100644 --- a/src/components/AdminDashboard.tsx +++ b/src/components/AdminDashboard.tsx @@ -1,7 +1,7 @@ import React, { useState, useEffect } from 'react'; -import { Users, ShieldCheck, Search, Loader2 } from 'lucide-react'; +import { Users, ShieldCheck, Search, Loader2, Settings, ShieldAlert } from 'lucide-react'; import { cn } from '../lib/utils'; -import { collection, onSnapshot, doc, updateDoc, addDoc, deleteDoc, query, orderBy } from 'firebase/firestore'; +import { collection, onSnapshot, doc, updateDoc, addDoc, deleteDoc, query, orderBy, getDoc, setDoc } from 'firebase/firestore'; import { db, handleFirestoreError, OperationType, auth } from '../lib/firebase'; import { ImageUploader } from './ImageUploader'; @@ -25,6 +25,9 @@ export const AdminDashboard: React.FC = () => { const [testText, setTestText] = useState(''); const [testImageUrls, setTestImageUrls] = useState([]); const [submittingTestimonial, setSubmittingTestimonial] = useState(false); + const [maintenanceMode, setMaintenanceMode] = useState(false); + const [maintenanceMessage, setMaintenanceMessage] = useState(''); + const [savingSettings, setSavingSettings] = useState(false); const handleCreateTestimonial = async (e: React.FormEvent) => { e.preventDefault(); @@ -70,12 +73,41 @@ export const AdminDashboard: React.FC = () => { console.error("Testimonials fetch error:", error); }); + const settingsRef = doc(db, 'settings', 'app'); + const unsubSettings = onSnapshot(settingsRef, (docSnap) => { + if (docSnap.exists()) { + setMaintenanceMode(docSnap.data().maintenanceMode || false); + setMaintenanceMessage(docSnap.data().maintenanceMessage || ''); + } + }, (error) => { + console.warn("Failed to listen to settings:", error); + }); + return () => { unsub(); unsubTestimonials(); + unsubSettings(); }; }, []); + const handleSaveSettings = async () => { + setSavingSettings(true); + try { + const settingsRef = doc(db, 'settings', 'app'); + // Using setDoc with merge to create it if it doesn't exist + await setDoc(settingsRef, { + maintenanceMode, + maintenanceMessage + }, { merge: true }); + alert('Configurações salvas com sucesso!'); + } catch (err) { + console.error(err); + alert('Erro ao salvar as configurações.'); + } finally { + setSavingSettings(false); + } + }; + const handleToggleApproval = async (userId: string, currentStatus: boolean) => { try { await updateDoc(doc(db, 'users', userId), { @@ -156,6 +188,61 @@ export const AdminDashboard: React.FC = () => { +
+
+ +

Configurações do Sistema

+
+ +
+
+
+
+ + Modo de Manutenção +
+ +
+

+ O modo de manutenção bloqueia o acesso ao aplicativo para todos os usuários comuns, exibindo uma tela de aviso. Somente administradores poderão acessar a plataforma. +

+
+ +
+ +