diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..628b6ce --- /dev/null +++ b/Dockerfile @@ -0,0 +1,20 @@ +# Use Node.js LTS version +FROM node:20-bookworm-slim + +# Set working directory +WORKDIR /app + +# Copy package files +COPY package*.json ./ + +# Install dependencies +RUN npm install + +# Copy project files +COPY . . + +# Expose the port Vite runs on +EXPOSE 3000 + +# Start the development server +CMD ["npm", "run", "dev"] diff --git a/README.md b/README.md index 0e8cd9c..21da801 100644 --- a/README.md +++ b/README.md @@ -2,43 +2,19 @@ GHBanner -# Aura Craft Studio: Shared UI Framework +# Run and deploy your AI Studio app -This repository provides a reusable UI skeleton with integrated Authentication, Theme management, and AI Connection services. It is designed to work seamlessly both in Google AI Studio (Studio Mode) and in standard deployed environments. +This contains everything you need to run your app locally. -## 🌟 High-Level Overview +View your app in AI Studio: https://ai.studio/apps/9f4101c2-8730-451b-9134-3b46f26726c6 -Aura Craft Studio is a modular foundation for building AI-powered applications. It abstracts away the complexities of: -- **Authentication & RBAC:** Seamless user identity management across different hosting modes. -- **AI Connectivity:** Pre-wired access to Google Gemini models via a secure proxy. -- **Theming:** A robust light/dark mode system powered by Tailwind CSS. -- **Cloud Storage:** Standardized operations for R2 storage (upload, download, list). -- **Architecture:** A strict Container/View pattern that ensures long-term scalability and code quality. +## Run Locally -## 📖 Critical Documentation +**Prerequisites:** Node.js -### 🚀 [instructions.md](./instructions.md) -The essential "Getting Started" guide. It details the initial setup of `metadata.json` and provides the **Mandatory Prompt Template** required for AI-accelerated feature development. -### 📜 [rules.md](./rules.md) -The "Source of Truth" for development boundaries. It defines which parts of the system are immutable (the "Wiring") and establishes coding standards like the 200-line file limit and modular feature grouping. - ---- - -## 💻 Run Locally - -**Prerequisites:** Node.js - -1. **Install dependencies:** +1. Install dependencies: `npm install` -2. **Set the API Key:** - Set the `GEMINI_API_KEY` in [.env.local](.env.local) to your Gemini API key. -3. **Run the app:** +2. Set the `GEMINI_API_KEY` in [.env.local](.env.local) to your Gemini API key +3. Run the app: `npm run dev` - ---- - -## 🛠️ Project Links - -- **AI Studio App:** [https://ai.studio/apps/drive/1eaFbkjczgCmq_TXULG7_eSOgkyaGX1Yk](https://ai.studio/apps/drive/1eaFbkjczgCmq_TXULG7_eSOgkyaGX1Yk) -- **Organization:** HumanizeIQ diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..99b5f3b --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,17 @@ +version: '3.8' + +services: + aura_central: + build: + context: . + dockerfile: Dockerfile + command: sh -lc "if [ ! -d node_modules ] || [ -z \"$(ls -A node_modules 2>/dev/null)\" ] || [ ! -d node_modules/@rollup/rollup-linux-arm64-musl ]; then npm ci --include=optional; fi; npm run dev" + ports: + - "3000:3000" + volumes: + - .:/app + - /app/node_modules + environment: + - GEMINI_API_KEY=${GEMINI_API_KEY} + stdin_open: true + tty: true diff --git a/docs/blueprint.md b/docs/blueprint.md new file mode 100644 index 0000000..fbd328e --- /dev/null +++ b/docs/blueprint.md @@ -0,0 +1,24 @@ +# Aura Central App Blueprint + +## Overview +Aura Central is a "Central Nervous System" application designed to be a personal AI assistant and productivity dashboard. It features a modern, high-density interface with real-time updates and multi-modal interaction (Chat, Voice, Meet). + +## Core Features +- **Multi-step Authentication**: A secure, invite-only onboarding process with email, phone, and 2FA (TOTP) verification. +- **Subscription Management**: A billing flow for managing AI credits and plan levels. +- **Intelligent Dashboard**: A centralized hub for tasks, schedule, files, and recent communications. +- **Multi-modal Assistant (Ask Aura)**: + - **Chat**: Text-based interaction with Gemini AI. + - **Voice**: Real-time voice interaction. + - **Meet**: Meeting transcription and summarization. +- **Contextual View Modes**: Toggle between "Professional" and "Personal" modes to filter relevant tasks and information. + +## Technical Stack +- **Frontend**: React with TypeScript. +- **Styling**: Tailwind CSS with custom theme variables. +- **Animations**: Framer Motion (motion/react). +- **Backend/Database**: Firebase (Firestore & Authentication). +- **AI Integration**: Google Gemini API via `@google/genai`. + +## Architecture +The app follows a Single Page Application (SPA) architecture. The main `App.tsx` handles the core layout and state management, while specialized components handle the authentication and subscription flows. Styling is modularized into several CSS files to separate concerns (theme, layout, components, etc.). diff --git a/docs/filespec.md b/docs/filespec.md new file mode 100644 index 0000000..6c86a93 --- /dev/null +++ b/docs/filespec.md @@ -0,0 +1,32 @@ +# Aura Central File Specification + +## Root Directory +- **.env.example**: Template for environment variables (e.g., Gemini API Key). +- **.gitignore**: Specifies files and directories to be ignored by Git. +- **firebase-applet-config.json**: Configuration for the Firebase project (API keys, project IDs). +- **firebase-blueprint.json**: Intermediate representation of the Firestore data structure. +- **firestore.rules**: Security rules for the Firestore database. +- **index.html**: The main entry point for the browser. +- **metadata.json**: Application metadata (name, description, permissions). +- **package.json**: Manages npm dependencies and scripts. +- **tsconfig.json**: TypeScript compiler configuration. +- **vite.config.ts**: Configuration for the Vite build tool. + +## /src Directory +- **App.tsx**: The main application component. It handles the core dashboard layout, state management, and view mode toggling. +- **firebase.ts**: Initializes the Firebase SDK and exports the database and authentication instances. +- **index.css**: The main entry point for CSS, importing all modular style files. +- **main.tsx**: The entry point for the React application, rendering the `App` component. + +## /src/components Directory +- **AuthFlow.tsx**: Implements the multi-step onboarding process (Welcome, Invite Code, Email, Phone, 2FA). +- **SubscriptionFlow.tsx**: Handles the billing and credit top-up process for Aura Pro subscriptions. + +## /src/styles Directory +- **aura.css**: Styles specific to the "Ask Aura" assistant (chat bubbles, suggestion pills, voice/meet circles). +- **auth.css**: Styles for the authentication and onboarding flow. +- **base.css**: Global base styles, resets, and custom scrollbar styling. +- **components.css**: Reusable UI component styles (cards, inputs, buttons, badges) defined as Tailwind utilities. +- **layout.css**: Styles for the main dashboard grid, header, and toolbar. +- **theme.css**: Defines the color palette, CSS variables, and dark mode configuration. +- **utilities.css**: Additional custom utility classes for the application. diff --git a/firebase-applet-config.json b/firebase-applet-config.json new file mode 100644 index 0000000..e5498e2 --- /dev/null +++ b/firebase-applet-config.json @@ -0,0 +1,10 @@ +{ + "projectId": "gen-lang-client-0408711807", + "appId": "1:1099064516953:web:2f3d7fa475bd4194546c48", + "apiKey": "AIzaSyB28OqUKvw94yNEbkHhSSPn43qdPce-1t4", + "authDomain": "gen-lang-client-0408711807.firebaseapp.com", + "firestoreDatabaseId": "ai-studio-9f4101c2-8730-451b-9134-3b46f26726c6", + "storageBucket": "gen-lang-client-0408711807.firebasestorage.app", + "messagingSenderId": "1099064516953", + "measurementId": "" +} \ No newline at end of file diff --git a/firebase-blueprint.json b/firebase-blueprint.json new file mode 100644 index 0000000..5ec23b6 --- /dev/null +++ b/firebase-blueprint.json @@ -0,0 +1,73 @@ +{ + "entities": { + "User": { + "title": "User", + "description": "A user of the Aura platform.", + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The user's full name." + }, + "email": { + "type": "string", + "format": "email", + "description": "The user's email address." + }, + "phone": { + "type": "string", + "description": "The user's phone number." + }, + "uid": { + "type": "string", + "description": "The user's unique Firebase Auth ID." + }, + "createdAt": { + "type": "string", + "format": "date-time", + "description": "When the user account was created." + } + }, + "required": ["name", "email", "phone", "uid", "createdAt"] + }, + "Subscription": { + "title": "Subscription", + "description": "A user's subscription and credit settings.", + "type": "object", + "properties": { + "plan": { + "type": "string", + "description": "The selected Aura plan." + }, + "topUpAmount": { + "type": "number", + "description": "The amount to top up." + }, + "rechargeLevel": { + "type": "number", + "description": "The balance level at which to auto-recharge." + }, + "uid": { + "type": "string", + "description": "The user's unique Firebase Auth ID." + }, + "updatedAt": { + "type": "string", + "format": "date-time", + "description": "When the subscription was last updated." + } + }, + "required": ["plan", "topUpAmount", "rechargeLevel", "uid", "updatedAt"] + } + }, + "firestore": { + "/users/{uid}": { + "schema": "User", + "description": "User profile data." + }, + "/subscriptions/{uid}": { + "schema": "Subscription", + "description": "User subscription data." + } + } +} diff --git a/firestore.rules b/firestore.rules new file mode 100644 index 0000000..cd8d81a --- /dev/null +++ b/firestore.rules @@ -0,0 +1,89 @@ +rules_version = '2'; +service cloud.firestore { + match /databases/{database}/documents { + // =============================================================== + // Assumed Data Model + // =============================================================== + // + // Collection: users + // Document ID: {uid} (Firebase Auth UID) + // Fields: + // - name: string (required, 1-100 chars) + // - email: string (required, valid email format) + // - phone: string (required, 1-20 chars) + // - uid: string (required, matches document ID) + // - createdAt: string (required, ISO 8601 format) + // + // Collection: subscriptions + // Document ID: {uid} (Firebase Auth UID) + // Fields: + // - plan: string (required, enum: ['Aura Pro']) + // - topUpAmount: number (required, positive) + // - rechargeLevel: number (required, positive) + // - uid: string (required, matches document ID) + // - updatedAt: string (required, ISO 8601 format) + // + // =============================================================== + + // =============================================================== + // Helper Functions + // =============================================================== + + function isAuthenticated() { + return request.auth != null; + } + + function isOwner(userId) { + return isAuthenticated() && request.auth.uid == userId; + } + + function isValidEmail(email) { + return email is string && email.matches("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"); + } + + function isValidDateString(dateStr) { + return dateStr is string && dateStr.matches("^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}.*Z?$"); + } + + function hasOnlyAllowedFields(fields) { + return request.resource.data.keys().hasOnly(fields); + } + + // Domain Validators + + function isValidUser(data) { + return hasOnlyAllowedFields(['name', 'email', 'phone', 'uid', 'createdAt']) && + data.name is string && data.name.size() > 0 && data.name.size() <= 100 && + isValidEmail(data.email) && + data.phone is string && data.phone.size() > 0 && data.phone.size() <= 20 && + data.uid == request.auth.uid && + isValidDateString(data.createdAt); + } + + function isValidSubscription(data) { + return hasOnlyAllowedFields(['plan', 'topUpAmount', 'rechargeLevel', 'uid', 'updatedAt']) && + data.plan in ['Aura Pro'] && + data.topUpAmount is number && data.topUpAmount > 0 && + data.rechargeLevel is number && data.rechargeLevel >= 0 && + data.uid == request.auth.uid && + isValidDateString(data.updatedAt); + } + + // =============================================================== + // Rules + // =============================================================== + + match /users/{uid} { + allow read, write: if true; + } + + match /subscriptions/{uid} { + allow read, write: if true; + } + + // Default deny + match /{path=**} { + allow read, write: if false; + } + } +} diff --git a/index.html b/index.html index 2ec10b3..282c624 100644 --- a/index.html +++ b/index.html @@ -1,57 +1,13 @@ - + - - Aura Craft Studio Template - - - - - + Aura Central + -
- - - - \ No newline at end of file + + + + diff --git a/metadata.json b/metadata.json index aa6ffcc..7152163 100644 --- a/metadata.json +++ b/metadata.json @@ -1,8 +1,7 @@ { - "name": "NOTSET", - "description": "A reusable UI skeleton with integrated Authentication, Theme management, and AI Connection services.", - "requestFramePermissions": [], - "organization": "HumanizeIQ", - "project": "Templates", - "component": "Frontend Template" -} \ No newline at end of file + "name": "Aura Central", + "description": "A comprehensive dashboard for managing tasks, schedules, and AI-driven insights.", + "requestFramePermissions": [ + "microphone" + ] +} diff --git a/package.json b/package.json index 0cec2ed..ab1b9e3 100644 --- a/package.json +++ b/package.json @@ -1,26 +1,36 @@ { - "name": "securechat", + "name": "react-example", "private": true, "version": "0.0.0", "type": "module", "scripts": { - "dev": "vite", + "dev": "vite --port=3000 --host=0.0.0.0", "build": "vite build", - "preview": "vite preview" + "preview": "vite preview", + "clean": "rm -rf dist", + "lint": "tsc --noEmit" }, "dependencies": { - "react": "^19.2.0", - "react-dom": "^19.2.0", - "@google/genai": "^1.24.0", - "openai": "4.28.0", - "pdfjs-dist": "3.11.174", - "mammoth": "1.6.0", - "xlsx": "0.18.5", - "jspdf": "2.5.1" + "@google/genai": "^1.29.0", + "@tailwindcss/vite": "^4.1.14", + "@vitejs/plugin-react": "^5.0.4", + "dotenv": "^17.2.3", + "express": "^4.21.2", + "firebase": "^12.11.0", + "lucide-react": "^0.546.0", + "motion": "^12.23.24", + "qrcode.react": "^4.2.0", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-markdown": "^10.1.0", + "vite": "^6.2.0" }, "devDependencies": { + "@types/express": "^4.17.21", "@types/node": "^22.14.0", - "@vitejs/plugin-react": "^5.0.0", + "autoprefixer": "^10.4.21", + "tailwindcss": "^4.1.14", + "tsx": "^4.21.0", "typescript": "~5.8.2", "vite": "^6.2.0" } diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..9462d10 --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,1239 @@ +import React, { useState, useRef, useEffect } from 'react'; +import { + CheckCircle2, + Clock, + Calendar, + Bell, + MoreHorizontal, + Plus, + Smartphone, + MessageSquare, + Mic, + Menu, + Smile, + Send, + FileText, + Image as ImageIcon, + ChevronRight, + Layout, + LayoutGrid, + Mail, + Settings, + Folder, + Briefcase, + User, + Zap, + Timer, + Target, + Moon, + Sun, + ShieldCheck, + Coins, + BarChart3, + Lock, + Shield, + Eye, + Fingerprint, + File, + Brain, + LogOut, + RefreshCw, + ChevronDown, + Square, + Play, + Pause, + X, + Video, + Copy +} from 'lucide-react'; +import { motion, AnimatePresence } from 'motion/react'; +import ReactMarkdown from 'react-markdown'; +import { GoogleGenAI } from "@google/genai"; +import { AuthFlow } from './components/AuthFlow'; +import { SubscriptionFlow } from './components/SubscriptionFlow'; +import { db, doc, getDoc } from './firebase'; + +// --- Types --- +interface Task { + id: string; + title: string; + category: 'Urgent' | 'Today' | 'Upcoming' | 'Waiting On'; + completed: boolean; + icon?: React.ReactNode; + type: 'Personal' | 'Professional'; +} + +interface ChatMessage { + role: 'user' | 'model'; + text: string; +} + +// --- Mock Data --- +const INITIAL_TASKS: Task[] = [ + { id: '1', title: 'Prepare for 2 PM meeting', category: 'Urgent', completed: false, icon: , type: 'Professional' }, + { id: '2', title: "Reply to Sarah's email", category: 'Urgent', completed: false, icon: , type: 'Professional' }, + { id: '3', title: 'Finish report for project X', category: 'Today', completed: false, icon: , type: 'Professional' }, + { id: '4', title: 'Call mom this evening', category: 'Today', completed: false, icon: , type: 'Personal' }, + { id: '5', title: 'Client presentation tomorrow', category: 'Upcoming', completed: false, icon: , type: 'Professional' }, + { id: '6', title: 'Book doctor appointment', category: 'Upcoming', completed: false, icon: , type: 'Personal' }, + { id: '7', title: 'Approval from John', category: 'Waiting On', completed: false, icon: , type: 'Professional' }, + { id: '8', title: 'Reply from Alex', category: 'Waiting On', completed: false, icon: , type: 'Personal' }, +]; + +const SCHEDULE = [ + { time: '2:00 PM', title: 'Project Meeting', icon: , type: 'Professional' }, + { time: '4:30 PM', title: 'Gym Session', icon: , type: 'Personal' }, + { time: '7:00 PM', title: 'Dinner with Kate', icon: , type: 'Personal' }, +]; + +const RECENT_FILES = [ + { name: 'Project X Report', type: 'doc', icon: , viewType: 'Professional' }, + { name: 'Budget Plan', type: 'sheet', icon: , viewType: 'Professional' }, + { name: 'Family Photo.jpg', type: 'image', icon: , viewType: 'Personal' }, +]; + +const RECENT_CHATS = [ + { name: 'Sarah', lastMsg: 'Need your feedback', icon: }, + { name: 'John', lastMsg: 'Sent the document', icon: }, +]; + +const NOTIFICATIONS = [ + { id: '1', title: 'New message from Sarah', time: '2m ago', icon: }, + { id: '2', title: 'Meeting starts in 15m', time: '15m ago', icon: }, + { id: '3', title: 'Project X report updated', time: '1h ago', icon: }, +]; + +// --- Components --- + +interface TaskItemProps { + task: Task; + onClick?: (task: Task) => void; +} + +const TaskItem: React.FC = ({ task, onClick }) => ( +
onClick?.(task)}> +
{task.icon || }
+ {task.title} + +
+); + +interface SectionProps { + title: string; + tasks: Task[]; + colorClass: string; + onTaskClick?: (task: Task) => void; +} + +const Section: React.FC = ({ title, tasks, colorClass, onTaskClick }) => ( +
+

{title}

+
+ {tasks.map(t => )} +
+
+); + +const ToolbarItem = ({ icon, label, active = false, hideLabel = false, onClick }: { icon: React.ReactNode, label: string, active?: boolean, hideLabel?: boolean, onClick?: () => void }) => ( +
+ {icon} + {!hideLabel && {label}} +
+); + +export default function App() { + const [user, setUser] = useState<{ name: string; email: string; phone: string; uid: string } | null>(null); + const [subscription, setSubscription] = useState<{ plan: string; topUpAmount: number; rechargeLevel: number; uid: string } | null>(null); + const [isAuthReady, setIsAuthReady] = useState(false); + const [expandedTile, setExpandedTile] = useState<'projects' | 'schedule' | 'files' | 'people' | 'billing' | 'health' | 'notifications' | null>(null); + const [isNotificationsExpanded, setIsNotificationsExpanded] = useState(false); + const [viewMode, setViewMode] = useState<'Personal' | 'Professional'>('Professional'); + const [isProfileDropdownOpen, setIsProfileDropdownOpen] = useState(false); + const [auraMode, setAuraMode] = useState<'chat' | 'voice' | 'meet'>('chat'); + const [isRecording, setIsRecording] = useState(false); + const [recordingTime, setRecordingTime] = useState(0); + const [theme, setTheme] = useState<'light' | 'dark'>(() => { + if (typeof window !== 'undefined') { + return (localStorage.getItem('aura_theme') as 'light' | 'dark') || 'light'; + } + return 'light'; + }); + const [tasks] = useState(INITIAL_TASKS); + const [chatInput, setChatInput] = useState(''); + const [messages, setMessages] = useState([ + { role: 'model', text: `Hi ${user?.name.split(' ')[0] || 'Alex'}, Welcome to Aura Central — AI for Understanding, Reasoning, and Action. Here's your update for today:` } + ]); + + useEffect(() => { + let interval: any; + if (isRecording) { + interval = setInterval(() => { + setRecordingTime((prev) => prev + 1); + }, 1000); + } else { + clearInterval(interval); + } + return () => clearInterval(interval); + }, [isRecording]); + + const formatTime = (seconds: number) => { + const mins = Math.floor(seconds / 60); + const secs = seconds % 60; + return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; + }; + + useEffect(() => { + const root = window.document.documentElement; + if (theme === 'dark') { + root.classList.add('dark'); + } else { + root.classList.remove('dark'); + } + localStorage.setItem('aura_theme', theme); + }, [theme]); + + const toggleTheme = () => { + setTheme(prev => prev === 'light' ? 'dark' : 'light'); + }; + + useEffect(() => { + const loadSavedData = async () => { + const savedUid = localStorage.getItem('aura_uid'); + if (savedUid) { + try { + // Fetch user data + const userDoc = await getDoc(doc(db, 'users', savedUid)); + if (userDoc.exists()) { + setUser(userDoc.data() as any); + } + // Fetch subscription data + const subDoc = await getDoc(doc(db, 'subscriptions', savedUid)); + if (subDoc.exists()) { + setSubscription(subDoc.data() as any); + } + } catch (error) { + console.error('Error loading saved data:', error); + } + } + setIsAuthReady(true); + }; + loadSavedData(); + }, []); + + useEffect(() => { + if (user) { + setMessages([{ role: 'model', text: `Hi ${user.name.split(' ')[0]}, Here's your update for today:` }]); + } + }, [user]); + const [isTyping, setIsTyping] = useState(false); + const [isMenuOpen, setIsMenuOpen] = useState(false); + const [isAppsMenuOpen, setIsAppsMenuOpen] = useState(false); + const chatEndRef = useRef(null); + const auraColRef = useRef(null); + const actionColRef = useRef(null); + const notificationsColRef = useRef(null); + const infoColRef = useRef(null); + const usageColRef = useRef(null); + + const scrollToCol = (ref: React.RefObject) => { + ref.current?.scrollIntoView({ behavior: 'smooth', inline: 'center', block: 'nearest' }); + setIsMenuOpen(false); + }; + + const scrollToBottom = () => { + chatEndRef.current?.scrollIntoView({ behavior: 'smooth' }); + }; + + useEffect(() => { + scrollToBottom(); + }, [messages]); + + const handleSendMessage = async () => { + if (!chatInput.trim()) return; + + const userMsg = chatInput; + setMessages(prev => [...prev, { role: 'user', text: userMsg }]); + setChatInput(''); + setIsTyping(true); + + try { + const apiKey = process.env.GEMINI_API_KEY; + if (!apiKey) { + throw new Error("GEMINI_API_KEY is not defined"); + } + const ai = new GoogleGenAI({ apiKey }); + const response = await ai.models.generateContent({ + model: "gemini-3-flash-preview", + contents: userMsg, + config: { + systemInstruction: "You are Ask Aura, a helpful personal AI assistant in the Aura Central dashboard. Keep responses concise and professional. You are talking to Alex." + } + }); + + setMessages(prev => [...prev, { role: 'model', text: response.text || "I'm sorry, I couldn't process that." }]); + } catch (error) { + console.error("Gemini Error:", error); + setMessages(prev => [...prev, { role: 'model', text: "Sorry, I'm having trouble connecting right now. Please check your API key." }]); + } finally { + setIsTyping(false); + } + }; + + const handleActionClick = async (item: any, type: 'task' | 'event' | 'file' | 'notification' | 'project') => { + let userPrompt = ""; + let contextData = ""; + + if (type === 'task') { + userPrompt = `I'm looking at the task: "${item.title}". Can you help me with this?`; + contextData = `Task Details: Title: ${item.title}, Category: ${item.category}, Status: ${item.completed ? 'Completed' : 'Pending'}, Type: ${item.type}`; + } else if (type === 'event') { + userPrompt = `I'm looking at my schedule: "${item.title}" at ${item.time}. What should I prepare?`; + contextData = `Event Details: Title: ${item.title}, Time: ${item.time}, Type: ${item.type}`; + } else if (type === 'file') { + userPrompt = `I'm looking at the file: "${item.name}". Can you summarize it or tell me more about it?`; + contextData = `File Details: Name: ${item.name}, Type: ${item.type}, View Context: ${item.viewType}`; + } else if (type === 'notification') { + userPrompt = `I just saw this notification: "${item.title}". What's the context?`; + contextData = `Notification Details: Title: ${item.title}, Time: ${item.time}`; + } else if (type === 'project') { + userPrompt = `I'm looking at the project: "${item.name}". It's at ${item.progress}% progress. What are the next steps?`; + contextData = `Project Details: Name: ${item.name}, Progress: ${item.progress}%`; + } + + setMessages(prev => [...prev, { role: 'user', text: userPrompt }]); + scrollToCol(auraColRef); + setIsTyping(true); + + try { + const apiKey = process.env.GEMINI_API_KEY; + if (!apiKey) throw new Error("GEMINI_API_KEY is not defined"); + + const ai = new GoogleGenAI({ apiKey }); + const response = await ai.models.generateContent({ + model: "gemini-3-flash-preview", + contents: `Context Information: ${contextData}\n\nUser Question: ${userPrompt}`, + config: { + systemInstruction: `You are Ask Aura, a helpful personal AI assistant. The user just clicked on an item in their dashboard, setting it as the current context. Use the provided Context Information to give a helpful, concise, and professional response. You are talking to Alex.` + } + }); + + setMessages(prev => [...prev, { role: 'model', text: response.text || "I'm sorry, I couldn't process that context." }]); + } catch (error) { + console.error("Gemini Error:", error); + setMessages(prev => [...prev, { role: 'model', text: "I'm having trouble analyzing that context right now." }]); + } finally { + setIsTyping(false); + } + }; + + if (!isAuthReady) { + return ( +
+ +
+ ); + } + + if (!user) { + return setUser(data)} />; + } + + if (!subscription) { + return setSubscription(data)} />; + } + + return ( +
+ {/* Header */} +
+ {/* Left: Logo & Title */} +
+
+ +
+
+
+

Aura Central

+
+ + +
+
+

+ AI for Understanding, Reasoning, and Action +

+
+
+ + {/* Center: Toolbar */} +
+ } label="Chat" active /> + } label="Explorer" /> + } label="Mail" /> + } label="Calendar" /> + } label="Settings" /> +
+ } label="All" /> +
+ + {/* Right: Profile Dropdown & Theme Toggle */} +
+ {/* Mobile Apps Button */} +
+ + + + {isAppsMenuOpen && ( + +
Select App
+ + + + + +
+ +
+ )} +
+
+ + {/* Profile Dropdown */} +
+ + + + {isProfileDropdownOpen && ( + + + +
+ +
+ )} +
+
+ + +
+
+ + {/* Main Grid */} +
+ + {/* Middle Column: Ask Aura - First in JSX for mobile swipeability */} +
+
+ +
+
+ + + + + {isMenuOpen && ( + + + + + + + + )} + +
+

Ask Aura

+
+ + + +
+
+ + + {auraMode === 'chat' ? ( + + +
+ + + + + +
+ +
+ + {messages.map((msg, i) => ( + +
+
+ {msg.text} +
+
+ {msg.role === 'model' && ( +
+ + +
+ )} +
+ ))} + {isTyping && ( + +
+
+
+
+
+
+
+
+ )} +
+
+
+ +
+ setChatInput(e.target.value)} + onKeyDown={(e) => e.key === 'Enter' && handleSendMessage()} + className="aura-input pr-24 text-sm" + /> +
+ + +
+
+ + ) : auraMode === 'voice' ? ( + +
+
+
+ +
+
+ {isRecording && ( +
+ + +
+ )} +
+ +

Aura Voice

+

+ {isRecording ? 'Aura is listening...' : 'Tap to start a voice conversation with Aura.'} +

+ + +
+ ) : ( + +
+
+ {isRecording ? ( + + + + ) : ( +
+
+ )} +
+ {isRecording && ( +
+ +
+ )} +
+ +

+ {isRecording ? 'Recording Meeting...' : 'Aura Meet'} +

+

+ {isRecording ? 'Aura is listening and transcribing your meeting in real-time.' : 'Start recording to have Aura transcribe, summarize, and extract action items from your meeting.'} +

+ +
+ {formatTime(recordingTime)} +
+ +
+ + + +
+ + {isRecording && ( + +
+
+ Live Transcript +
+

+ "So the main goal for Q2 is to increase user retention by 15% through the new loyalty program..." +

+
+ )} +
+ )} + +
+ + {/* Left Column Group: Actionables & Projects */} +
+ {/* Card 1: Actionables */} +
+
+
+ + + + {isMenuOpen && ( + + + + + + + + )} + +
+

+ {viewMode === 'Professional' ? 'Actionables' : 'Personal Hub'} +

+
+
+ +
+
+
t.category === 'Urgent' && t.type === viewMode)} + colorClass="aura-text-accent-red" + onTaskClick={(t) => handleActionClick(t, 'task')} + /> +
t.category === 'Today' && t.type === viewMode)} + colorClass="aura-text-accent-blue" + onTaskClick={(t) => handleActionClick(t, 'task')} + /> +
t.category === 'Upcoming' && t.type === viewMode)} + colorClass="aura-text-accent-emerald" + onTaskClick={(t) => handleActionClick(t, 'task')} + /> +
t.category === 'Waiting On' && t.type === viewMode)} + colorClass="aura-text-accent-amber" + onTaskClick={(t) => handleActionClick(t, 'task')} + /> +
+
+
+
+ + {/* Right Column Group: Information & Usage Stats */} +
+ {/* Card 4: Information */} +
+
+
+ + + + {isMenuOpen && ( + + + + + + + )} + +
+
+ +
+ + {!expandedTile ? ( + + {/* Notifications Tile - Spans 2 columns */} + +
+
+ + Notifications +
+ +
+
+ {NOTIFICATIONS.slice(0, 2).map((n) => ( + handleActionClick(n, 'notification')} + className="flex items-center gap-3 p-2 rounded-xl bg-aura-bg/50 border border-aura-border/50 cursor-pointer hover:bg-aura-bg transition-colors" + > +
+ {n.icon} +
+
+

{n.title}

+

{n.time}

+
+
+ ))} +
+
+ {/* Projects Cube */} + setExpandedTile('projects')} + className="aspect-square p-4 rounded-3xl bg-aura-surface border border-aura-border flex flex-col items-center justify-center text-center group transition-all cursor-pointer" + > +
+ +
+ 03 + Projects +
+ + {/* Schedule Cube */} + setExpandedTile('schedule')} + className="aspect-square p-4 rounded-3xl bg-aura-surface border border-aura-border flex flex-col items-center justify-center text-center group transition-all cursor-pointer" + > +
+ +
+ 2 PM + Schedule +
+ + {/* File Cube */} + setExpandedTile('files')} + className="aspect-square p-4 rounded-3xl bg-aura-surface border border-aura-border flex flex-col items-center justify-center text-center group transition-all cursor-pointer" + > +
+ +
+ 12 + Files +
+ + {/* People Cube */} + setExpandedTile('people')} + className="aspect-square p-4 rounded-3xl bg-aura-surface border border-aura-border flex flex-col items-center justify-center text-center group transition-all cursor-pointer" + > +
+ +
+ 05 + People +
+ + {/* Billing Cube */} + setExpandedTile('billing')} + className="aspect-square p-4 rounded-3xl bg-aura-surface border border-aura-border flex flex-col items-center justify-center text-center group transition-all cursor-pointer" + > +
+ +
+ $42 + Billing +
+ + {/* Health Cube */} + setExpandedTile('health')} + className="aspect-square p-4 rounded-3xl bg-aura-surface border border-aura-border flex flex-col items-center justify-center text-center group transition-all cursor-pointer" + > +
+ +
+ 98% + Health +
+
+ ) : ( + +
+ + {expandedTile} +
+ +
+ {expandedTile === 'notifications' && ( +
+ {NOTIFICATIONS.map((n) => ( + handleActionClick(n, 'notification')} + className="aura-row-card p-3 group hover:bg-aura-bg transition-colors cursor-pointer" + > +
+ {n.icon} +
+
+

{n.title}

+

{n.time}

+
+
+ ))} +
+ )} + + {expandedTile === 'projects' && ( +
+ {[ + { name: 'Project X', progress: 65, color: 'aura-bg-accent-blue' }, + { name: 'Client Campaign', progress: 40, color: 'aura-bg-accent-indigo' }, + { name: 'Budget Revision', progress: 85, color: 'aura-bg-accent-emerald' } + ].map((p, i) => ( +
handleActionClick(p, 'project')}> +
+ {p.name} + {p.progress}% +
+
+ +
+
+ ))} +
+ )} + + {expandedTile === 'schedule' && ( +
+ {SCHEDULE.filter(item => item.type === viewMode).map((item, i) => ( +
handleActionClick(item, 'event')}> +
{item.time}
+
+ {item.icon} + {item.title} +
+
+ ))} +
+ )} + + {expandedTile === 'files' && ( +
+ {RECENT_FILES.filter(file => file.viewType === viewMode).map((file, i) => ( +
handleActionClick(file, 'file')}> +
+ {file.icon} +
+ {file.name} + +
+ ))} +
+ )} + + {expandedTile === 'people' && ( +
+ {RECENT_CHATS.map((chat, i) => ( +
+
+ {chat.icon} +
+
+ {chat.name} + {chat.lastMsg} +
+
+
+ ))} +
+ )} + + {expandedTile === 'billing' && ( +
+
+
+ Plan + {subscription?.plan} +
+
+
+
+
+
+
+ Credits + 100% +
+
+ Storage + 42% +
+
+
+ )} + + {expandedTile === 'health' && ( +
+
+
+
+ +
+
+

System Health

+

All systems operational

+
+
+ 98% +
+
+ )} +
+
+ )} +
+
+
+
+
+
+ ); +} diff --git a/src/components/AuthFlow.tsx b/src/components/AuthFlow.tsx new file mode 100644 index 0000000..f9a6595 --- /dev/null +++ b/src/components/AuthFlow.tsx @@ -0,0 +1,1129 @@ +import React, { useState, useEffect, useRef } from 'react'; +import { motion, AnimatePresence } from 'motion/react'; +import { + User, + Mail, + Phone, + AlertCircle, + ShieldCheck, + ArrowRight, + ChevronLeft, + ChevronRight, + CheckCircle2, + Smartphone, + Lock, + Zap, + Brain, + Target, + Calendar, + MessageSquare, + FileText, + Shield, + Coins, + Mic, + Globe, + Bell, + RefreshCw, + KeyRound, + LogIn, + BookOpen, + Heart, + GraduationCap, + Users, + Library, + LayoutGrid, + X +} from 'lucide-react'; +import { QRCodeSVG } from 'qrcode.react'; +import { auth, db, doc, setDoc, signInWithGoogle, OperationType, handleFirestoreError } from '../firebase'; +import { PrivacyPolicy } from './PrivacyPolicy'; + +const BENEFITS = [ + { + title: "Privacy & Security", + description: "Enterprise-grade encryption and advanced threat detection.", + icon: , + color: "blue" + }, + { + title: "Peak Productivity", + description: "Streamline your workflow with intelligent automation.", + icon: , + color: "orange" + }, + { + title: "Autonomous Assistant", + description: "Works across WhatsApp, Web, and Voice, always synchronized.", + icon: , + color: "blue" + }, + { + title: "Agent Library", + description: "Growing collection of specialized agents for any task.", + icon: , + color: "indigo" + }, + { + title: "Social Impact", + description: "Your usage funds scholarships for students in need.", + icon: , + color: "rose" + } +]; + +type AuthStep = + | 'WELCOME' + | 'PRIVACY_POLICY' + | 'COLLECT_INVITE_CODE' + | 'COLLECT_EMAIL' + | 'COLLECT_NICKNAME' + | 'COLLECT_FULL_NAME' + | 'COLLECT_PHONE' + | 'VERIFY_EMAIL' + | 'VERIFY_PHONE' + | 'SETUP_TOTP' + | 'VERIFY_TOTP' + | 'SUCCESS'; + +interface AuthFlowProps { + onComplete: (userData: { name: string; email: string; phone: string; uid: string }) => void; +} + +export const AuthFlow: React.FC = ({ onComplete }) => { + const [step, setStep] = useState('WELCOME'); + const [showMobileBenefits, setShowMobileBenefits] = useState(false); + const [showPrivacyPolicy, setShowPrivacyPolicy] = useState(false); + const [mobileCarouselIndex, setMobileCarouselIndex] = useState(0); + const scrollContainerRef = useRef(null); + const isAutoScrolling = useRef(false); + const [currentBenefitIndex, setCurrentBenefitIndex] = useState(0); + const [inviteCode, setInviteCode] = useState(''); + const [nickname, setNickname] = useState(''); + const [fullName, setFullName] = useState(''); + const [email, setEmail] = useState(''); + const [phone, setPhone] = useState(''); + const [uid, setUid] = useState(() => { + const saved = localStorage.getItem('aura_uid'); + if (saved) return saved; + const newId = 'user_' + Math.random().toString(36).substr(2, 9); + localStorage.setItem('aura_uid', newId); + return newId; + }); + const [emailOtp, setEmailOtp] = useState(''); + const [phoneOtp, setPhoneOtp] = useState(''); + const [totpCode, setTotpCode] = useState(''); + const [isProcessing, setIsProcessing] = useState(false); + const [error, setError] = useState(null); + const inputRef = useRef(null); + + useEffect(() => { + if (inputRef.current) { + inputRef.current.focus(); + } + }, [step]); + + useEffect(() => { + if (step === 'WELCOME') { + const interval = setInterval(() => { + setCurrentBenefitIndex((prev) => (prev + 1) % BENEFITS.length); + }, 3000); + return () => clearInterval(interval); + } + }, [step]); + + useEffect(() => { + if (showMobileBenefits) { + const totalItems = BENEFITS.length + 1; // +1 for Enterprise card + const interval = setInterval(() => { + setMobileCarouselIndex((prev) => (prev + 1) % totalItems); + }, 5000); + return () => clearInterval(interval); + } + }, [showMobileBenefits]); + + useEffect(() => { + if (scrollContainerRef.current) { + const container = scrollContainerRef.current; + const targetScroll = mobileCarouselIndex * container.clientWidth; + + if (Math.abs(container.scrollLeft - targetScroll) > 5) { + isAutoScrolling.current = true; + container.scrollTo({ + left: targetScroll, + behavior: 'smooth' + }); + + const timeout = setTimeout(() => { + isAutoScrolling.current = false; + }, 600); + return () => clearTimeout(timeout); + } + } + }, [mobileCarouselIndex]); + + const saveUserToFirestore = async () => { + const userData = { + name: fullName || nickname, + nickname, + fullName, + email, + phone, + uid, + createdAt: new Date().toISOString() + }; + try { + await setDoc(doc(db, 'users', uid), userData); + return userData; + } catch (error) { + console.error('Firestore Error (Simulated Auth):', error); + return userData; // Fallback for demo if rules block + } + }; + + const handleNext = async (e?: React.FormEvent) => { + if (e) e.preventDefault(); + setIsProcessing(true); + + // Simulate API delay + await new Promise(resolve => setTimeout(resolve, 800)); + setIsProcessing(false); + + switch (step) { + case 'WELCOME': + setError(null); + setStep('COLLECT_INVITE_CODE'); + break; + case 'COLLECT_INVITE_CODE': + if (inviteCode) { + if (inviteCode.toUpperCase() === 'AURA-2026') { + setStep('COLLECT_EMAIL'); + setError(null); + } else { + setError('Invalid invite code. Access denied.'); + setStep('WELCOME'); + } + } + break; + case 'COLLECT_EMAIL': + if (email) setStep('COLLECT_NICKNAME'); + break; + case 'COLLECT_NICKNAME': if (nickname) setStep('COLLECT_FULL_NAME'); break; + case 'COLLECT_FULL_NAME': if (fullName) setStep('COLLECT_PHONE'); break; + case 'COLLECT_PHONE': if (phone) setStep('VERIFY_EMAIL'); break; + case 'VERIFY_EMAIL': if (emailOtp === '123456') setStep('VERIFY_PHONE'); else alert('Use 123456 for demo'); break; + case 'VERIFY_PHONE': if (phoneOtp === '123456') setStep('SETUP_TOTP'); else alert('Use 123456 for demo'); break; + case 'SETUP_TOTP': setStep('VERIFY_TOTP'); break; + case 'VERIFY_TOTP': + if (totpCode === '123456') { + setIsProcessing(true); + const userData = await saveUserToFirestore(); + setIsProcessing(false); + setStep('SUCCESS'); + } else { + alert('Use 123456 for demo'); + } + break; + case 'SUCCESS': onComplete({ name: fullName || nickname, email, phone, uid }); break; + } + }; + + const renderStep = () => { + switch (step) { + case 'WELCOME': + return ( + +
+ {/* Left Side: Hero */} +
+ {/* Background Decorative Element */} +
+
+ +
+
+
+ +
+
+

+ Aura Central +

+ + AI for Understanding, Reasoning, and Action. + +
+
+ +
+
+

+ AI for Understanding, Reasoning, and Action. + Your Lifestyle Guide in an AI world +

+
+ + {error && ( + + + {error} + + )} + + {/* Benefits Preview Card - Mobile Only */} + +
+
+ +
+ + {/* HumanizeIQ Branding */} +
+
+ Humanize
IQ
Logo +
+
+

A Product From

+

HumanizeIQ

+

Profit for Good (PFG) Company

+
+
+
+ + {/* Bottom Stats/Trust */} +
+
+

99.9%

+

Uptime

+
+
+

256-bit

+

Security

+
+
+

Global

+

Access

+
+
+
+ + {/* Right Side: Benefit Card Cubes */} +
+
+
+

Platform Benefits

+ +
+ {/* Combined Benefit: Privacy & Security */} +
+
+
+ +
+
+ +
+
+

Privacy & Security

+

+ Enterprise-grade encryption and advanced threat detection. Your data remains yours. +

+ +
+ + {/* Benefit Cube 3 */} +
+
+ +
+

Peak Productivity

+

+ Streamline your workflow with intelligent automation for mundane tasks. +

+
+ + {/* Autonomous Assistant Cube */} +
+
+
+ +
+

Autonomous Assistant

+
+

+ Works across WhatsApp, Web, and Voice, always synchronized. +

+
+ + + +
+
+ + {/* Agent Library */} +
+
+ +
+

Agent Library

+

+ Growing collection of specialized agents: Presentation, Tutor, Research, and more. +

+
+ + {/* Combined Benefit: Social Impact & Community - 2 Slots */} +
+
+
+ +
+
+ +
+
+
+
The Aura Difference
+

Why Choose Aura?

+

+ By using Aura, you support students receiving paid training to survive in an AI world. + Our profits fund scholarships and stipends for those in need. +

+
+
+
+
+
+ + {/* Enterprise Level Plan - Aligned to Bottom */} +
+
+
+
+
Standard for All
+

Enterprise Level Plan

+

Premium features unlocked for every consumer.

+
+
    +
  • Bundled Usage
  • +
  • Pay-as-you-go
  • +
  • Never Expire
  • +
  • Profit-Sharing
  • +
+
+ +
+
+ $7.99 + /mo +
+
+ Rewards system based on company profits. +
+ +
+
+
+
+ + {/* Mobile Benefits Overlay */} + + {showMobileBenefits && ( + + {/* Content - Full screen horizontal cards */} +
{ + if (isAutoScrolling.current) return; + const container = e.currentTarget; + if (container.clientWidth === 0) return; + const index = Math.round(container.scrollLeft / container.clientWidth); + if (index !== mobileCarouselIndex) { + setMobileCarouselIndex(index); + } + }} + className="flex-grow overflow-x-auto snap-x snap-mandatory no-scrollbar flex h-full" + > + {BENEFITS.map((benefit, idx) => ( +
+
+ {/* Close Button inside card */} +
+ +
+ + {/* Decorative Background for Card */} +
+
+ +
+ {React.cloneElement(benefit.icon as React.ReactElement<{ className?: string }>, { className: "w-12 h-12 text-aura-accent-blue" })} +
+ +

{benefit.title}

+

+ {benefit.description} +

+ +
+ {benefit.title === "Privacy & Security" && ( + + )} + + {benefit.title === "Social Impact" && ( +
+

PFG Initiative

+

Your usage directly funds scholarships for students in the AI era.

+
+ )} + +
+
+
+ ))} + + {/* Mobile Enterprise Plan Card */} +
+
+ {/* Close Button inside card */} +
+ +
+ +
+
Standard for All
+

Enterprise Access

+

Premium features unlocked for every consumer.

+
+ +
    +
  • Bundled Usage
  • +
  • Pay-as-you-go
  • +
  • Never Expire
  • +
  • Profit-Sharing
  • +
+ +
+
+ $7.99 + /mo +
+
+
+
+
+ + {/* Final CTA in Overlay removed to focus on information */} + + {/* Navigation Arrows - Positioned at 2/3 marker */} +
+ +
+ +
+ +
+ + {/* Navigation Dots at Bottom */} +
+
+ {Array.from({ length: BENEFITS.length + 1 }).map((_, i) => ( +
+ ))} +
+
+ + )} + +
+
+ ); + + case 'PRIVACY_POLICY': + return null; // Handled as overlay + + case 'COLLECT_INVITE_CODE': + return ( + +
+ +
+ Invite Only +

Enter your invite code

+
+
+
+

AI for Understanding, Reasoning, and Action

+

Aura is currently in private beta. Please enter your access code to begin.

+
+ +
+ + setInviteCode(e.target.value)} + className="auth-input-field uppercase tracking-widest" + required + /> + + +
+ ); + + case 'COLLECT_EMAIL': + return ( + +
+ +
+ Step 01 +

What's your email?

+
+
+
+

We'll validate your invite code with this email address.

+
+
+ + setEmail(e.target.value)} + className="auth-input-field" + required + /> + + +

Demo: Use AURA-2026

+
+ ); + + case 'COLLECT_NICKNAME': + return ( + +
+ +
+ Step 02 +

What should we call you?

+
+
+
+

This is how Aura will address you.

+
+
+ + setNickname(e.target.value)} + className="auth-input-field" + required + /> + + +
+ ); + + case 'COLLECT_FULL_NAME': + return ( + +
+ +
+ Step 03 +

And your full name?

+
+
+
+

For your official profile and documents.

+
+
+ + setFullName(e.target.value)} + className="auth-input-field" + required + /> + + +
+ ); + + case 'COLLECT_PHONE': + return ( + +
+ +
+ Step 04 +

And your phone number?

+
+
+
+

We'll use this for secure verification.

+
+
+ + setPhone(e.target.value)} + className="auth-input-field" + required + /> + + +
+ ); + + case 'VERIFY_EMAIL': + return ( + +
+ +
+ Verification +

Check your email

+
+
+
+

We sent a 6-digit code to {email}

+
+
+
+ + setEmailOtp(e.target.value)} + className="aura-input py-5 pl-14 pr-6 text-3xl tracking-[0.5em] font-mono" + required + /> +
+

Demo: Use 123456

+ +
+
+ ); + + case 'VERIFY_PHONE': + return ( + +
+ +
+ Verification +

One more code

+
+
+
+

We sent a text to {phone}

+
+
+
+ + setPhoneOtp(e.target.value)} + className="aura-input py-5 pl-14 pr-6 text-3xl tracking-[0.5em] font-mono" + required + /> +
+

Demo: Use 123456

+ +
+
+ ); + + case 'SETUP_TOTP': + return ( + +
+ +
+ Security +

Setup 2FA

+
+
+
+

Scan this QR code with Google Authenticator or Authy.

+
+ +
+ +
+ +
+

Manual Secret

+ JBSWY3DPEHPK3PXP +
+ + +
+ ); + + case 'VERIFY_TOTP': + return ( + +
+ +
+ Final Step +

Verify App Code

+
+
+
+

Enter the 6-digit code from your authenticator app.

+
+
+
+ + setTotpCode(e.target.value)} + className="aura-input py-5 pl-14 pr-6 text-3xl tracking-[0.5em] font-mono" + required + /> +
+

Demo: Use 123456

+ +
+
+ ); + + case 'SUCCESS': + return ( + +
+ +
+
+

You're all set!

+

Welcome to the future of productivity, {nickname || fullName.split(' ')[0]}.

+
+ +
+ ); + } + }; + + return ( +
+ {/* Background Accents */} +
+
+
+
+ +
+ + {renderStep()} + + + {/* Privacy Policy Overlay */} + + {showPrivacyPolicy && ( + setShowPrivacyPolicy(false)} /> + )} + +
+ + {/* Progress Indicator */} + {step !== 'WELCOME' && step !== 'SUCCESS' && ( +
+ {['COLLECT_INVITE_CODE', 'COLLECT_EMAIL', 'COLLECT_NICKNAME', 'COLLECT_FULL_NAME', 'COLLECT_PHONE', 'VERIFY_EMAIL', 'VERIFY_PHONE', 'SETUP_TOTP', 'VERIFY_TOTP'].map((s, i) => ( +
+ ))} +
+ )} +
+ ); +}; diff --git a/src/components/PrivacyPolicy.tsx b/src/components/PrivacyPolicy.tsx new file mode 100644 index 0000000..e5a371c --- /dev/null +++ b/src/components/PrivacyPolicy.tsx @@ -0,0 +1,90 @@ +import React from 'react'; +import { Shield, X, ArrowLeft } from 'lucide-react'; +import { motion } from 'motion/react'; + +interface PrivacyPolicyProps { + onBack: () => void; +} + +export const PrivacyPolicy: React.FC = ({ onBack }) => { + return ( + + {/* Header with Close Button */} +
+
+ + Privacy Policy +
+ +
+ +
+
+
+
+ +
+
+

Privacy Policy

+

Last Updated: March 23, 2026

+
+
+ +
+
+

1. Introduction

+

+ At Aura Central, we take your privacy seriously. This Privacy Policy explains how we collect, use, and protect your personal information when you use our AI-powered platform. +

+
+ +
+

2. Data Collection

+

+ We collect information you provide directly to us, such as your name, email address, and any data you input into our AI tools. We also collect technical data like your IP address and device information to improve our services. +

+
+ +
+

3. Data Usage

+

+ Your data is used solely to provide and improve the Aura Central experience. We do not sell your personal data to third parties. Your interactions with our AI are used to provide personalized responses and are protected by enterprise-grade encryption. +

+
+ +
+

4. Security

+

+ We implement industry-standard security measures to protect your data, including 256-bit encryption and secure data centers. Access to your data is strictly controlled and monitored. +

+
+ +
+

5. Your Rights

+

+ You have the right to access, correct, or delete your personal information at any time. If you have any questions about your data, please contact our privacy team. +

+
+
+ +
+

+ By using Aura Central, you agree to the terms of this Privacy Policy. If you do not agree, please do not use the platform. +

+
+
+
+
+ ); +}; diff --git a/src/components/SubscriptionFlow.tsx b/src/components/SubscriptionFlow.tsx new file mode 100644 index 0000000..ed35b13 --- /dev/null +++ b/src/components/SubscriptionFlow.tsx @@ -0,0 +1,202 @@ +import React, { useState } from 'react'; +import { motion, AnimatePresence } from 'motion/react'; +import { + Zap, + Check, + CreditCard, + ArrowRight, + ShieldCheck, + TrendingUp, + RefreshCw, + Coins +} from 'lucide-react'; +import { db, doc, setDoc, OperationType, handleFirestoreError } from '../firebase'; + +interface SubscriptionData { + plan: string; + topUpAmount: number; + rechargeLevel: number; + uid: string; +} + +interface SubscriptionFlowProps { + uid: string; + onComplete: (data: SubscriptionData) => void; +} + +export const SubscriptionFlow: React.FC = ({ uid, onComplete }) => { + const [topUpAmount, setTopUpAmount] = useState(50); + const [rechargeLevel, setRechargeLevel] = useState(10); + const [isProcessing, setIsProcessing] = useState(false); + + const handleComplete = async () => { + setIsProcessing(true); + const subData = { + plan: 'Aura Central Platform', + topUpAmount, + rechargeLevel, + uid, + updatedAt: new Date().toISOString() + }; + try { + await setDoc(doc(db, 'subscriptions', uid), subData); + setIsProcessing(false); + onComplete(subData); + } catch (error) { + handleFirestoreError(error, OperationType.CREATE, `subscriptions/${uid}`); + setIsProcessing(false); + } + }; + + return ( +
+ {/* Background Accents */} +
+
+
+
+ + + {/* Left: Plan Details */} +
+
+
+ +
+

Choose your Aura

+

Power your productivity with intelligent credits.

+
+ +
+
+ +
+
+
+
+

Aura Central Platform

+

Monthly Access Fee

+
+
+ $29 + /mo +
+
+
+ {[ + 'Unified Communication Hub', + 'Omnichannel AI Assistant', + 'WhatsApp, Web, & Voice', + 'Priority Security & Privacy' + ].map((feature, i) => ( +
+ + {feature} +
+ ))} +
+
+
+ +
+ +
+

Secure Billing

+

Encrypted by industry-standard protocols.

+
+
+
+ + {/* Right: Usage Configuration */} +
+
+
+
+
+ +
+

Usage Configuration

+
+

Set up your pay-as-you-go usage credits. You only pay for what you consume.

+ +
+ +
+ {[25, 50, 100].map((amount) => ( + + ))} +
+
+
+ +
+
+
+ +
+

Auto-Recharge

+
+ +
+
+ + ${rechargeLevel} +
+ setRechargeLevel(parseInt(e.target.value))} + className="w-full h-2 bg-slate-200 dark:bg-slate-800 rounded-lg appearance-none cursor-pointer accent-indigo-600" + /> +

+ We'll automatically top up your account with ${topUpAmount} when your balance falls below ${rechargeLevel}. +

+
+
+
+ +
+
+ Total Due Today + ${29 + topUpAmount} +
+ +
+
+
+
+ ); +}; diff --git a/src/firebase.ts b/src/firebase.ts new file mode 100644 index 0000000..fe28a04 --- /dev/null +++ b/src/firebase.ts @@ -0,0 +1,88 @@ +import { initializeApp } from 'firebase/app'; +import { getAuth, GoogleAuthProvider, signInWithPopup, onAuthStateChanged, User as FirebaseUser } from 'firebase/auth'; +import { getFirestore, doc, setDoc, getDoc, onSnapshot, getDocFromServer } from 'firebase/firestore'; +import firebaseConfig from '../firebase-applet-config.json'; + +// Initialize Firebase SDK +const app = initializeApp(firebaseConfig); +export const db = getFirestore(app, firebaseConfig.firestoreDatabaseId); +export const auth = getAuth(app); +export const googleProvider = new GoogleAuthProvider(); + +// Auth helpers +export const signInWithGoogle = async () => { + try { + const result = await signInWithPopup(auth, googleProvider); + return result.user; + } catch (error) { + console.error('Error signing in with Google:', error); + throw error; + } +}; + +// Firestore Error Handling +export enum OperationType { + CREATE = 'create', + UPDATE = 'update', + DELETE = 'delete', + LIST = 'list', + GET = 'get', + WRITE = 'write', +} + +export interface FirestoreErrorInfo { + error: string; + operationType: OperationType; + path: string | null; + authInfo: { + userId: string | undefined; + email: string | null | undefined; + emailVerified: boolean | undefined; + isAnonymous: boolean | undefined; + tenantId: string | null | undefined; + providerInfo: { + providerId: string; + displayName: string | null; + email: string | null; + photoUrl: string | null; + }[]; + } +} + +export function handleFirestoreError(error: unknown, operationType: OperationType, path: string | null) { + const errInfo: FirestoreErrorInfo = { + error: error instanceof Error ? error.message : String(error), + authInfo: { + userId: auth.currentUser?.uid, + email: auth.currentUser?.email, + emailVerified: auth.currentUser?.emailVerified, + isAnonymous: auth.currentUser?.isAnonymous, + tenantId: auth.currentUser?.tenantId, + providerInfo: auth.currentUser?.providerData.map(provider => ({ + providerId: provider.providerId, + displayName: provider.displayName, + email: provider.email, + photoUrl: provider.photoURL + })) || [] + }, + operationType, + path + } + console.error('Firestore Error: ', JSON.stringify(errInfo)); + throw new Error(JSON.stringify(errInfo)); +} + +// Connection test +async function testConnection() { + try { + await getDocFromServer(doc(db, 'test', 'connection')); + } catch (error) { + if(error instanceof Error && error.message.includes('the client is offline')) { + console.error("Please check your Firebase configuration. "); + } + } +} +testConnection(); + +export { doc, setDoc, getDoc, onSnapshot }; +export type { FirebaseUser }; diff --git a/src/index.css b/src/index.css new file mode 100644 index 0000000..0f7896f --- /dev/null +++ b/src/index.css @@ -0,0 +1,29 @@ +@import "tailwindcss"; + +@utility no-scrollbar { + -ms-overflow-style: none; + scrollbar-width: none; + &::-webkit-scrollbar { + display: none; + } +} + +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap'); + +/* Theme & Variables */ +@import "./styles/theme.css"; + +/* Base Styles & Scrollbars */ +@import "./styles/base.css"; + +/* Reusable UI Components */ +@import "./styles/components.css"; + +/* Layout Styles */ +@import "./styles/layout.css"; + +/* Assistant Specific Styles */ +@import "./styles/aura.css"; + +/* Authentication Flow Styles */ +@import "./styles/auth.css"; diff --git a/src/main.tsx b/src/main.tsx new file mode 100644 index 0000000..080dac3 --- /dev/null +++ b/src/main.tsx @@ -0,0 +1,10 @@ +import {StrictMode} from 'react'; +import {createRoot} from 'react-dom/client'; +import App from './App.tsx'; +import './index.css'; + +createRoot(document.getElementById('root')!).render( + + + , +); diff --git a/src/styles/aura.css b/src/styles/aura.css new file mode 100644 index 0000000..96e9c10 --- /dev/null +++ b/src/styles/aura.css @@ -0,0 +1,67 @@ +@layer components { + .aura-chat-bubble-user { + @apply max-w-[85%] p-4 rounded-[1rem] text-sm text-white rounded-tr-none shadow-sm; + background: linear-gradient(135deg, var(--aura-primary), var(--aura-accent-indigo)); + } + + .aura-chat-bubble-model { + @apply max-w-[85%] p-4 rounded-[1rem] text-sm rounded-tl-none border border-aura-border shadow-sm; + background-color: var(--aura-surface); + color: var(--aura-text); + } + + .aura-message-actions { + @apply flex items-center gap-1 mt-2 transition-opacity duration-200; + } + + @media (min-width: 1024px) { + .aura-message-container:hover .aura-message-actions { + @apply opacity-100; + } + .aura-message-actions { + @apply opacity-0; + } + } + + @media (max-width: 1023px) { + .aura-message-actions { + @apply opacity-100; + } + } + + .aura-suggestion-pill-blue { + @apply flex items-center gap-2 px-3 py-1.5 rounded-full border text-xs font-medium whitespace-nowrap transition-colors bg-blue-50 dark:bg-blue-900/20 border-blue-100 dark:border-blue-800/50 hover:bg-blue-100 dark:hover:bg-blue-900/30; + color: var(--aura-accent-blue); + } + + .aura-suggestion-pill-orange { + @apply flex items-center gap-2 px-3 py-1.5 rounded-full border text-xs font-medium whitespace-nowrap transition-colors bg-orange-50 dark:bg-orange-900/20 border-orange-100 dark:border-orange-800/50 hover:bg-orange-100 dark:hover:bg-orange-900/30; + color: var(--aura-accent-orange); + } + + .aura-suggestion-pill-indigo { + @apply flex items-center gap-2 px-3 py-1.5 rounded-full border text-xs font-medium whitespace-nowrap transition-colors bg-indigo-50 dark:bg-indigo-900/20 border-indigo-100 dark:border-indigo-800/50 hover:bg-indigo-100 dark:hover:bg-indigo-900/30; + color: var(--aura-accent-indigo); + } + + .aura-suggestion-pill-red { + @apply flex items-center gap-2 px-3 py-1.5 rounded-full border text-xs font-medium whitespace-nowrap transition-colors bg-red-50 dark:bg-red-900/20 border-red-100 dark:border-red-800/50 hover:bg-red-100 dark:hover:bg-red-900/30; + color: var(--aura-accent-red); + } + + .aura-voice-circle { + @apply w-40 h-40 rounded-full flex items-center justify-center transition-all duration-500; + } + + .aura-voice-core { + @apply w-24 h-24 rounded-full flex items-center justify-center shadow-xl transition-all duration-300; + } + + .aura-meet-circle { + @apply w-32 h-32 rounded-full flex items-center justify-center transition-all duration-500; + } + + .aura-meet-core { + @apply w-16 h-16 rounded-full flex items-center justify-center shadow-lg transition-all; + } +} diff --git a/src/styles/auth.css b/src/styles/auth.css new file mode 100644 index 0000000..c9e0649 --- /dev/null +++ b/src/styles/auth.css @@ -0,0 +1,45 @@ +@layer components { + .auth-container { + @apply min-h-screen flex items-center justify-center p-6 bg-aura-bg relative overflow-hidden; + } + + .auth-card { + @apply aura-glass-card p-12 w-full max-w-xl flex flex-col items-center text-center relative z-10; + } + + .auth-step-container { + @apply space-y-8 w-full max-w-md; + } + + .auth-input-container { + @apply relative; + } + + .auth-input-icon { + @apply absolute left-5 top-1/2 -translate-y-1/2 text-slate-400 w-6 h-6; + } + + .auth-input-field { + @apply aura-input py-5 pl-14 pr-6 text-xl; + } + + .auth-submit-btn { + @apply absolute right-3 top-1/2 -translate-y-1/2 bg-slate-900 dark:bg-slate-100 text-white dark:text-slate-900 p-3 rounded-xl hover:bg-blue-600 dark:hover:bg-blue-400 disabled:opacity-50 transition-all; + } + + .auth-progress-indicator { + @apply fixed bottom-12 left-1/2 -translate-x-1/2 flex gap-2; + } + + .auth-progress-dot { + @apply h-1.5 rounded-full transition-all duration-500; + } + + .auth-progress-dot-active { + @apply w-8 bg-blue-600; + } + + .auth-progress-dot-inactive { + @apply w-2 bg-slate-200 dark:bg-slate-800; + } +} diff --git a/src/styles/base.css b/src/styles/base.css new file mode 100644 index 0000000..65af4df --- /dev/null +++ b/src/styles/base.css @@ -0,0 +1,49 @@ +@layer base { + body { + @apply font-sans antialiased transition-colors duration-300; + background: radial-gradient(circle at 50% 0%, var(--bg-gradient-start) 0%, var(--bg-gradient-end) 100%); + color: var(--aura-text); + } + + /* Custom scrollbar */ + ::-webkit-scrollbar { + width: 6px; + } + ::-webkit-scrollbar-track { + background: transparent; + } + ::-webkit-scrollbar-thumb { + background: #cbd5e1; + border-radius: 10px; + } + .dark ::-webkit-scrollbar-thumb { + background: #334155; + } + ::-webkit-scrollbar-thumb:hover { + background: #94a3b8; + } + .dark ::-webkit-scrollbar-thumb:hover { + background: #475569; + } + + .no-scrollbar::-webkit-scrollbar { + display: none; + } + .no-scrollbar { + -ms-overflow-style: none; + scrollbar-width: none; + } + + .markdown-body p { + @apply mb-2 last:mb-0; + } + .markdown-body ul { + @apply list-disc list-inside mb-2; + } + .markdown-body ol { + @apply list-decimal list-inside mb-2; + } + .markdown-body strong { + @apply font-bold; + } +} diff --git a/src/styles/components.css b/src/styles/components.css new file mode 100644 index 0000000..bddf477 --- /dev/null +++ b/src/styles/components.css @@ -0,0 +1,108 @@ +@utility aura-card { + @apply backdrop-blur-md border rounded-xl transition-all duration-300 shadow-sm; + background-color: color-mix(in srgb, var(--aura-surface), transparent 5%); + border-color: var(--aura-border); +} + +@utility aura-input { + @apply w-full rounded-xl py-3 px-4 transition-all outline-none border-2; + background-color: var(--aura-surface); + border-color: var(--aura-border); + color: var(--aura-text); +} + +@utility aura-glass-card { + @apply backdrop-blur-xl border rounded-3xl shadow-2xl; + background-color: color-mix(in srgb, var(--aura-surface), transparent 20%); + border-color: var(--aura-border); +} + +@layer components { + .aura-card-hover { + @apply hover:shadow-md hover:border-blue-500/30 dark:hover:border-blue-400/30; + } + + .aura-btn-primary { + @apply px-6 py-3 rounded-xl font-bold transition-all flex items-center justify-center gap-2 shadow-lg; + background-color: var(--aura-text); + color: var(--aura-surface); + } + + .aura-btn-primary:hover { + @apply opacity-90 scale-[1.02]; + } + + .aura-btn-secondary { + @apply px-4 py-2 rounded-xl font-semibold transition-all flex items-center justify-center gap-2 border; + background-color: var(--aura-surface); + border-color: var(--aura-border); + color: var(--aura-text); + } + + .aura-btn-secondary:hover { + @apply bg-slate-50 dark:bg-slate-800 border-slate-300 dark:border-slate-600; + } + + .aura-input:focus { + @apply border-blue-500/50 ring-4 ring-blue-500/10; + } + + .aura-badge { + @apply px-2 py-0.5 rounded-md text-[10px] font-bold uppercase tracking-wider; + } + + .aura-logo { + @apply w-10 h-10 rounded-xl flex items-center justify-center shadow-lg; + background: linear-gradient(135deg, #3b82f6 0%, #6366f1 100%); + box-shadow: 0 10px 15px -3px rgba(59, 130, 246, 0.2); + } + + .aura-toggle-group { + @apply flex p-1 rounded-2xl border shadow-inner; + background-color: color-mix(in srgb, var(--aura-bg), var(--aura-text) 5%); + border-color: var(--aura-border); + } + + .aura-gradient-card { + @apply rounded-3xl p-6 text-white shadow-2xl relative overflow-hidden; + background: linear-gradient(135deg, #2563eb 0%, #4f46e5 100%); + box-shadow: 0 20px 25px -5px rgba(37, 99, 235, 0.3); + } + + .aura-progress-track { + @apply h-1 w-full rounded-full overflow-hidden; + background-color: var(--aura-border); + } + + .aura-avatar-container { + @apply w-24 h-24 rounded-full overflow-hidden border-4 shadow-lg; + border-color: var(--aura-border); + } + + .aura-row-card { + @apply flex items-center gap-3 p-2 rounded-xl transition-colors; + background-color: color-mix(in srgb, var(--aura-bg), var(--aura-text) 2%); + } + + .aura-row-card:hover { + @apply bg-slate-100 dark:bg-slate-800; + } + + .aura-gradient-top-bar { + @apply absolute top-0 left-0 w-full h-1; + background: linear-gradient(to right, #60a5fa, #6366f1, #a855f7); + } + + .section-title { + @apply text-[10px] font-bold uppercase tracking-widest mb-3; + color: var(--aura-text-muted); + } + + .item-row { + @apply flex items-center gap-3 p-2 rounded-lg transition-colors cursor-pointer; + } + + .item-row:hover { + @apply bg-slate-100/50 dark:bg-slate-800/50; + } +} diff --git a/src/styles/layout.css b/src/styles/layout.css new file mode 100644 index 0000000..50dc826 --- /dev/null +++ b/src/styles/layout.css @@ -0,0 +1,47 @@ +@layer components { + .aura-dashboard { + @apply min-h-screen p-4 bg-aura-bg transition-colors duration-300 w-full; + } + + .aura-header { + @apply flex items-center justify-between mb-6 px-4 gap-6; + } + + .aura-main-grid { + @apply flex overflow-x-auto snap-x snap-mandatory xl:grid xl:grid-cols-12 gap-4 mb-4 h-[calc(100vh-120px)] min-h-[600px] xl:min-h-[750px] no-scrollbar; + } + + .aura-col-action { + @apply w-full flex-shrink-0 snap-center xl:w-auto xl:flex-shrink xl:snap-none xl:col-span-2 aura-card p-4 flex flex-col h-full overflow-hidden; + } + + .aura-col-aura { + @apply w-full flex-shrink-0 snap-center xl:w-auto xl:flex-shrink xl:snap-none xl:col-span-7 aura-card p-5 flex flex-col h-full relative overflow-hidden; + } + + .aura-col-info { + @apply w-full flex-shrink-0 snap-center xl:w-auto xl:flex-shrink xl:snap-none xl:col-span-3 aura-card p-4 flex flex-col h-full overflow-hidden; + } + + .aura-bottom-grid { + @apply grid grid-cols-1 xl:grid-cols-3 gap-4; + } + + .aura-toolbar { + @apply flex items-center gap-1 backdrop-blur-sm p-1.5 rounded-2xl border shadow-sm; + background-color: color-mix(in srgb, var(--aura-surface), transparent 50%); + border-color: color-mix(in srgb, var(--aura-border), transparent 50%); + } + + .aura-toolbar-item { + @apply flex items-center gap-2 rounded-xl cursor-pointer transition-all; + } + + .aura-toolbar-item-active { + @apply bg-aura-surface shadow-sm text-aura-accent-blue; + } + + .aura-toolbar-item-inactive { + @apply text-aura-text-muted hover:bg-aura-surface/50 hover:text-aura-text; + } +} diff --git a/src/styles/theme.css b/src/styles/theme.css new file mode 100644 index 0000000..ce74443 --- /dev/null +++ b/src/styles/theme.css @@ -0,0 +1,62 @@ +@theme { + --font-sans: "Inter", ui-sans-serif, system-ui, sans-serif; + --font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, monospace; + + --color-aura-bg: var(--aura-bg); + --color-aura-surface: var(--aura-surface); + --color-aura-primary: var(--aura-primary); + --color-aura-text: var(--aura-text); + --color-aura-text-muted: var(--aura-text-muted); + --color-aura-border: var(--aura-border); + --color-aura-accent: var(--aura-accent); + --color-aura-accent-blue: var(--aura-accent-blue); + --color-aura-accent-emerald: var(--aura-accent-emerald); + --color-aura-accent-red: var(--aura-accent-red); + --color-aura-accent-amber: var(--aura-accent-amber); + --color-aura-accent-indigo: var(--aura-accent-indigo); + --color-aura-accent-purple: var(--aura-accent-purple); + --color-aura-accent-orange: var(--aura-accent-orange); +} + +:root { + --aura-bg: #f1f5f9; + --aura-surface: #ffffff; + --aura-primary: #2563eb; + --aura-primary-hover: #1d4ed8; + --aura-text: #0f172a; + --aura-text-muted: #475569; + --aura-border: #cbd5e1; + --aura-accent: #2563eb; + --aura-accent-blue: #2563eb; + --aura-accent-emerald: #059669; + --aura-accent-red: #dc2626; + --aura-accent-amber: #d97706; + --aura-accent-indigo: #4f46e5; + --aura-accent-purple: #9333ea; + --aura-accent-orange: #ea580c; + + --bg-gradient-start: #e2e8f0; + --bg-gradient-end: #f1f5f9; +} + +.dark { + --aura-bg: #020617; + --aura-surface: #0f172a; + --aura-primary: #059669; + --aura-primary-hover: #047857; + --aura-text: #f8fafc; + --aura-text-muted: #94a3b8; + --aura-border: #1e293b; + --aura-accent: #059669; + --aura-accent-blue: #60a5fa; + --aura-accent-emerald: #059669; + --aura-accent-red: #f87171; + --aura-accent-amber: #fbbf24; + --aura-accent-indigo: #818cf8; + --aura-accent-purple: #c084fc; + --aura-accent-orange: #fb923c; + + --bg-gradient-start: #0f172a; + --bg-gradient-end: #020617; + color-scheme: dark; +} diff --git a/src/styles/utilities.css b/src/styles/utilities.css new file mode 100644 index 0000000..6018195 --- /dev/null +++ b/src/styles/utilities.css @@ -0,0 +1,28 @@ +/* Custom scrollbar */ +::-webkit-scrollbar { + width: 6px; +} +::-webkit-scrollbar-track { + background: transparent; +} +::-webkit-scrollbar-thumb { + background: #cbd5e1; + border-radius: 10px; +} +.dark ::-webkit-scrollbar-thumb { + background: #334155; +} +::-webkit-scrollbar-thumb:hover { + background: #94a3b8; +} +.dark ::-webkit-scrollbar-thumb:hover { + background: #475569; +} + +.no-scrollbar::-webkit-scrollbar { + display: none; +} +.no-scrollbar { + -ms-overflow-style: none; + scrollbar-width: none; +} diff --git a/tsconfig.json b/tsconfig.json index 2c6eed5..d88f175 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,9 +10,6 @@ "DOM.Iterable" ], "skipLibCheck": true, - "types": [ - "node" - ], "moduleResolution": "bundler", "isolatedModules": true, "moduleDetection": "force", @@ -26,4 +23,4 @@ "allowImportingTsExtensions": true, "noEmit": true } -} \ No newline at end of file +} diff --git a/vite.config.ts b/vite.config.ts index 824cf45..0506f1b 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,28 +1,24 @@ -import path from 'path'; -import { fileURLToPath } from 'url'; -import { defineConfig, loadEnv } from 'vite'; +import tailwindcss from '@tailwindcss/vite'; import react from '@vitejs/plugin-react'; +import path from 'path'; +import {defineConfig, loadEnv} from 'vite'; -// FIX: Derive __dirname in ESM environment to resolve "Cannot find name '__dirname'" error -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); - -export default defineConfig(({ mode }) => { - const env = loadEnv(mode, '.', ''); - return { - server: { - port: 3000, - host: '0.0.0.0', +export default defineConfig(({mode}) => { + const env = loadEnv(mode, '.', ''); + return { + plugins: [react(), tailwindcss()], + define: { + 'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY), + }, + resolve: { + alias: { + '@': path.resolve(__dirname, '.'), }, - plugins: [react()], - define: { - 'process.env.API_KEY': JSON.stringify(env.GEMINI_API_KEY), - 'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY) - }, - resolve: { - alias: { - '@': path.resolve(__dirname, '.'), - } - } - }; -}); \ No newline at end of file + }, + server: { + // HMR is disabled in AI Studio via DISABLE_HMR env var. + // Do not modify—file watching is disabled to prevent flickering during agent edits. + hmr: process.env.DISABLE_HMR !== 'true', + }, + }; +});