diff --git a/firestore.rules b/firestore.rules new file mode 100644 index 0000000..8b92e9b --- /dev/null +++ b/firestore.rules @@ -0,0 +1,66 @@ +rules_version = '2'; +service cloud.firestore { + match /databases/{database}/documents { + // Helper function to check if the current user is an admin + function isAdmin() { + return request.auth != null && + request.auth.token.email != null && + exists(/databases/$(database)/documents/admins/$(request.auth.token.email.toLowerCase())); + } + + // Rules for the admins collection + match /admins/{email} { + allow read: if request.auth != null && (request.auth.token.email.toLowerCase() == email.toLowerCase() || isAdmin()); + allow write: if isAdmin(); + } + + // Rules for user profiles + match /profiles/{userId} { + allow read: if request.auth != null; + allow create, update: if request.auth != null && request.auth.uid == userId; + allow delete: if isAdmin(); + } + + // Rules for stories + match /stories/{storyId} { + allow read: if true; + allow create: if request.auth != null; + allow update: if request.auth != null && (request.auth.uid == resource.data.author_id || isAdmin()); + allow delete: if isAdmin(); + } + + // Rules for reactions + match /reactions/{reactionId} { + allow read: if true; + allow create, update: if request.auth != null; + allow delete: if request.auth != null && (request.auth.uid == resource.data.user_id || isAdmin()); + } + + // Rules for reports + match /reports/{reportId} { + allow read: if isAdmin(); + allow create: if request.auth != null; + allow update, delete: if isAdmin(); + } + + // Rules for ngos + match /ngos/{ngoId} { + allow read: if true; + allow write: if isAdmin(); + } + + // Rules for ngo_requests + match /ngo_requests/{requestId} { + allow read: if isAdmin(); + allow create: if request.auth != null; + allow update, delete: if isAdmin(); + } + + // Rules for testimonials + match /testimonials/{testimonialId} { + allow read: if true; + allow create: if request.auth != null; + allow update, delete: if isAdmin(); + } + } +} diff --git a/package-lock.json b/package-lock.json index 914c19b..ba470b5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -107,7 +107,6 @@ "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.26.2", @@ -1000,7 +999,6 @@ "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.13.1.tgz", "integrity": "sha512-0O33PKrXLoIWkoOO5ByFaLjZehBctSYWnb+xJkIdx2SKP/K9l1UPFXPwASyrOIqyY3ws+7orF/1j7wI5EKzPYQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@firebase/component": "0.6.17", "@firebase/logger": "0.4.4", @@ -1067,7 +1065,6 @@ "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.4.1.tgz", "integrity": "sha512-9VGjnY23Gc1XryoF/ABWtZVJYnaPOnjHM7dsqq9YALgKRtxI1FryvELUVkDaEIUf4In2bfkb9ZENF1S9M273Dw==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@firebase/app": "0.13.1", "@firebase/component": "0.6.17", @@ -1083,8 +1080,7 @@ "version": "0.9.3", "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.3.tgz", "integrity": "sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw==", - "license": "Apache-2.0", - "peer": true + "license": "Apache-2.0" }, "node_modules/@firebase/auth-compat": { "version": "0.5.27", @@ -1535,7 +1531,6 @@ "integrity": "sha512-Z4rK23xBCwgKDqmzGVMef+Vb4xso2j5Q8OG0vVL4m4fA5ZjPMYQazu8OJJC3vtQRC3SQ/Pgx/6TPNVsCd70QRw==", "hasInstallScript": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "tslib": "^2.1.0" }, @@ -2234,7 +2229,6 @@ "resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-5.10.0.tgz", "integrity": "sha512-PTigkxMdMUP6B5ISS7jMqJAKhgrhZwjprDqR1eATtFfh0OpKVNp110xiH+goeVdrJ29/4LeZJR4FaHHWstsu0A==", "license": "MIT", - "peer": true, "engines": { "node": ">=12.16" } @@ -2335,7 +2329,6 @@ "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.105.4.tgz", "integrity": "sha512-cEnx+k49knU+qdIP7rXwR6fqEXPHZs+74xFK1R0S8MgQ7v9tbePVdGxvO03n3bPympMdJWVLadARBfU4TgNHCQ==", "license": "MIT", - "peer": true, "dependencies": { "@supabase/auth-js": "2.105.4", "@supabase/functions-js": "2.105.4", @@ -2490,7 +2483,6 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.8.1.tgz", "integrity": "sha512-hQUVn2Lij2NAxVFEdvIGxT9gP1tq2yM83m+by3whWFsWC+1y8pxxxHUFE1UqDu2VsGi2i6RLcv4QvouM84U+ow==", "dev": true, - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.8.1", "@typescript-eslint/types": "8.8.1", @@ -2724,7 +2716,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2966,7 +2957,6 @@ "url": "https://github.com/sponsors/ai" } ], - "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001663", "electron-to-chromium": "^1.5.28", @@ -3315,8 +3305,7 @@ "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "peer": true + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, "node_modules/data-uri-to-buffer": { "version": "4.0.1", @@ -3528,7 +3517,6 @@ "integrity": "sha512-hB4FIzXovouYzwzECDcUkJ4OcfOEkXTv2zRY6B9bkwjx/cprAq0uvm1nl7zvQ0/TsUk0zQiN4uPfJpB9m+rPMQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -5177,7 +5165,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -5453,7 +5440,6 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", - "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -5465,7 +5451,6 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", - "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -6243,7 +6228,6 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -6327,7 +6311,6 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", "dev": true, - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -6447,7 +6430,6 @@ "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -6541,7 +6523,6 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, diff --git a/src/App.tsx b/src/App.tsx index e9c0b9e..5ec1f3c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,7 @@ import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; import { ThemeProvider } from './context/ThemeContext'; +import { SafetyProvider, useSafety } from './context/SafetyContext'; +import DisguiseView from './components/DisguiseView'; import { Toaster } from 'react-hot-toast'; import Navbar from './components/Navbar'; import Home from './pages/Home'; @@ -21,14 +23,20 @@ import NotFound from './pages/NotFound'; -function App() { +function AppContent() { + const { isDisguised } = useSafety(); + return ( - - - - {/* // Global back-to-top button available across all pages */} - -
+ + + {/* // Global back-to-top button available across all pages */} + +
+ {isDisguised && } +
@@ -48,9 +56,19 @@ function App() {
-
- + +
+
+ ); +} + +function App() { + return ( + + + + ); } diff --git a/src/components/DisguiseView.tsx b/src/components/DisguiseView.tsx new file mode 100644 index 0000000..dac0dff --- /dev/null +++ b/src/components/DisguiseView.tsx @@ -0,0 +1,323 @@ +import React, { useState, useEffect } from 'react'; +import { useSafety } from '../context/SafetyContext'; +import { + CheckCircle, + Calendar as CalendarIcon, + CloudSun, + ListTodo, + Settings, + LogOut, + Plus, + Trash2, + FileText, + Bell, + Search, + ChevronRight +} from 'lucide-react'; + +export default function DisguiseView() { + const { toggleDisguise } = useSafety(); + const [time, setTime] = useState(new Date()); + + // Task list state + const [tasks, setTasks] = useState<{ id: string; text: string; completed: boolean }[]>([ + { id: '1', text: 'Review quarterly financial presentation', completed: false }, + { id: '2', text: 'Finalize developer API documentation updates', completed: true }, + { id: '3', text: 'Confirm schedule for team sync call on Friday', completed: false }, + { id: '4', text: 'Draft email blast for product newsletter', completed: false } + ]); + const [newTask, setNewTask] = useState(''); + + // Calendar state + const [selectedDay, setSelectedDay] = useState(new Date().getDate()); + + // Note state + const [note, setNote] = useState('Meeting notes - June 2026\n- Review product roadmap draft\n- Follow up on security compliance report\n- Confirm UX design milestones\n'); + + // Update clock every second + useEffect(() => { + const timer = setInterval(() => setTime(new Date()), 1000); + return () => clearInterval(timer); + }, []); + + const handleAddTask = (e: React.FormEvent) => { + e.preventDefault(); + if (!newTask.trim()) return; + setTasks(prev => [...prev, { id: Date.now().toString(), text: newTask.trim(), completed: false }]); + setNewTask(''); + }; + + const handleToggleTask = (id: string) => { + setTasks(prev => prev.map(t => t.id === id ? { ...t, completed: !t.completed } : t)); + }; + + const handleDeleteTask = (id: string) => { + setTasks(prev => prev.filter(t => t.id !== id)); + }; + + const daysInMonth = Array.from({ length: 30 }, (_, i) => i + 1); + + return ( +
+ {/* Sidebar Camouflage */} + + + {/* Main Camouflage Content Area */} +
+ {/* Top Header */} +
+
+

Dashboard Overview

+

Welcome back, John. Here is your productivity summary.

+
+ +
+ {/* Clock Widget */} +
+ + {time.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' })} + + + {time.toLocaleDateString([], { weekday: 'long', month: 'short', day: 'numeric' })} + +
+ +
+
+ + {/* Dashboard Grid */} +
+ {/* Left Column: Tasks and Notes */} +
+ + {/* Task Manager Card */} +
+
+

+ + Task Manager +

+ + {tasks.filter(t => !t.completed).length} pending + +
+ + {/* Add Task Form */} +
+ setNewTask(e.target.value)} + placeholder="Create a new task..." + className="flex-1 px-4 py-2 border border-slate-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 bg-slate-50" + /> + +
+ + {/* Tasks Checklist */} +
+ {tasks.map(task => ( +
+ + +
+ ))} +
+
+ + {/* Quick Notes Card */} +
+

+ + Notepad +

+