Merge ux_aura_central files into ux_aura_assistant - taking ux_aura_central versions for conflicting files
This commit is contained in:
20
Dockerfile
Normal file
20
Dockerfile
Normal file
@@ -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"]
|
||||
40
README.md
40
README.md
@@ -2,43 +2,19 @@
|
||||
<img width="1200" height="475" alt="GHBanner" src="https://github.com/user-attachments/assets/0aa67016-6eaf-458a-adb2-6e31a0763ed6" />
|
||||
</div>
|
||||
|
||||
# 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.
|
||||
|
||||
## 📖 Critical Documentation
|
||||
|
||||
### 🚀 [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
|
||||
## 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
|
||||
|
||||
17
docker-compose.yml
Normal file
17
docker-compose.yml
Normal file
@@ -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
|
||||
24
docs/blueprint.md
Normal file
24
docs/blueprint.md
Normal file
@@ -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.).
|
||||
32
docs/filespec.md
Normal file
32
docs/filespec.md
Normal file
@@ -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.
|
||||
10
firebase-applet-config.json
Normal file
10
firebase-applet-config.json
Normal file
@@ -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": ""
|
||||
}
|
||||
73
firebase-blueprint.json
Normal file
73
firebase-blueprint.json
Normal file
@@ -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."
|
||||
}
|
||||
}
|
||||
}
|
||||
89
firestore.rules
Normal file
89
firestore.rules
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
52
index.html
52
index.html
@@ -1,57 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="./vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Aura Craft Studio Template</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script>
|
||||
// Configure Tailwind to use the 'class' strategy for dark mode
|
||||
tailwind.config = {
|
||||
darkMode: 'class',
|
||||
}
|
||||
</script>
|
||||
<script type="importmap">
|
||||
{
|
||||
"imports": {
|
||||
"react/": "https://aistudiocdn.com/react@^19.2.0/",
|
||||
"react": "https://aistudiocdn.com/react@^19.2.0",
|
||||
"react-dom/": "https://aistudiocdn.com/react-dom@^19.2.0/",
|
||||
"@google/genai": "https://aistudiocdn.com/@google/genai@^1.24.0",
|
||||
"openai": "https://esm.sh/openai@4.28.0",
|
||||
"pdfjs-dist": "https://esm.sh/pdfjs-dist@3.11.174",
|
||||
"mammoth": "https://esm.sh/mammoth@1.6.0",
|
||||
"xlsx": "https://esm.sh/xlsx@0.18.5",
|
||||
"jspdf": "https://esm.sh/jspdf@2.5.1",
|
||||
"path": "https://esm.sh/path@^0.12.7",
|
||||
"vite": "https://esm.sh/vite@^7.3.0",
|
||||
"@vitejs/plugin-react": "https://esm.sh/@vitejs/plugin-react@^5.1.2",
|
||||
"url": "https://esm.sh/url@^0.11.4"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<link rel="stylesheet" href="/index.css">
|
||||
<title>Aura Central</title>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
// IIFE to set theme from local storage before React loads
|
||||
(function() {
|
||||
try {
|
||||
const theme = localStorage.getItem('theme');
|
||||
if (theme === 'dark') {
|
||||
document.documentElement.classList.add('dark');
|
||||
} else {
|
||||
// Default to light theme if no theme is set or it's 'light'
|
||||
document.documentElement.classList.remove('dark');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Could not set theme from local storage", e);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="./index.tsx"></script>
|
||||
<script type="module" src="/index.tsx"></script>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
@@ -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"
|
||||
"name": "Aura Central",
|
||||
"description": "A comprehensive dashboard for managing tasks, schedules, and AI-driven insights.",
|
||||
"requestFramePermissions": [
|
||||
"microphone"
|
||||
]
|
||||
}
|
||||
34
package.json
34
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"
|
||||
}
|
||||
|
||||
1239
src/App.tsx
Normal file
1239
src/App.tsx
Normal file
@@ -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: <Clock className="w-4 h-4 text-red-500" />, type: 'Professional' },
|
||||
{ id: '2', title: "Reply to Sarah's email", category: 'Urgent', completed: false, icon: <Bell className="w-4 h-4 text-red-500" />, type: 'Professional' },
|
||||
{ id: '3', title: 'Finish report for project X', category: 'Today', completed: false, icon: <FileText className="w-4 h-4 text-blue-500" />, type: 'Professional' },
|
||||
{ id: '4', title: 'Call mom this evening', category: 'Today', completed: false, icon: <User className="w-4 h-4 text-emerald-500" />, type: 'Personal' },
|
||||
{ id: '5', title: 'Client presentation tomorrow', category: 'Upcoming', completed: false, icon: <Briefcase className="w-4 h-4 text-indigo-500" />, type: 'Professional' },
|
||||
{ id: '6', title: 'Book doctor appointment', category: 'Upcoming', completed: false, icon: <Calendar className="w-4 h-4 text-teal-500" />, type: 'Personal' },
|
||||
{ id: '7', title: 'Approval from John', category: 'Waiting On', completed: false, icon: <CheckCircle2 className="w-4 h-4 text-amber-500" />, type: 'Professional' },
|
||||
{ id: '8', title: 'Reply from Alex', category: 'Waiting On', completed: false, icon: <MessageSquare className="w-4 h-4 text-orange-500" />, type: 'Personal' },
|
||||
];
|
||||
|
||||
const SCHEDULE = [
|
||||
{ time: '2:00 PM', title: 'Project Meeting', icon: <FileText className="w-4 h-4 text-blue-500" />, type: 'Professional' },
|
||||
{ time: '4:30 PM', title: 'Gym Session', icon: <Target className="w-4 h-4 text-emerald-500" />, type: 'Personal' },
|
||||
{ time: '7:00 PM', title: 'Dinner with Kate', icon: <User className="w-4 h-4 text-orange-500" />, type: 'Personal' },
|
||||
];
|
||||
|
||||
const RECENT_FILES = [
|
||||
{ name: 'Project X Report', type: 'doc', icon: <FileText className="w-4 h-4 text-blue-500" />, viewType: 'Professional' },
|
||||
{ name: 'Budget Plan', type: 'sheet', icon: <Layout className="w-4 h-4 text-emerald-500" />, viewType: 'Professional' },
|
||||
{ name: 'Family Photo.jpg', type: 'image', icon: <ImageIcon className="w-4 h-4 text-purple-500" />, viewType: 'Personal' },
|
||||
];
|
||||
|
||||
const RECENT_CHATS = [
|
||||
{ name: 'Sarah', lastMsg: 'Need your feedback', icon: <MessageSquare className="w-4 h-4 text-blue-500" /> },
|
||||
{ name: 'John', lastMsg: 'Sent the document', icon: <MessageSquare className="w-4 h-4 text-blue-500" /> },
|
||||
];
|
||||
|
||||
const NOTIFICATIONS = [
|
||||
{ id: '1', title: 'New message from Sarah', time: '2m ago', icon: <MessageSquare className="w-4 h-4 text-blue-500" /> },
|
||||
{ id: '2', title: 'Meeting starts in 15m', time: '15m ago', icon: <Clock className="w-4 h-4 text-amber-500" /> },
|
||||
{ id: '3', title: 'Project X report updated', time: '1h ago', icon: <FileText className="w-4 h-4 text-emerald-500" /> },
|
||||
];
|
||||
|
||||
// --- Components ---
|
||||
|
||||
interface TaskItemProps {
|
||||
task: Task;
|
||||
onClick?: (task: Task) => void;
|
||||
}
|
||||
|
||||
const TaskItem: React.FC<TaskItemProps> = ({ task, onClick }) => (
|
||||
<div className="item-row group cursor-pointer" onClick={() => onClick?.(task)}>
|
||||
<div className="flex-shrink-0">{task.icon || <CheckCircle2 className="w-4 h-4 aura-text-muted" />}</div>
|
||||
<span className="text-sm aura-text-main flex-grow">{task.title}</span>
|
||||
<MoreHorizontal className="w-4 h-4 aura-text-muted opacity-0 group-hover:opacity-100 transition-opacity" />
|
||||
</div>
|
||||
);
|
||||
|
||||
interface SectionProps {
|
||||
title: string;
|
||||
tasks: Task[];
|
||||
colorClass: string;
|
||||
onTaskClick?: (task: Task) => void;
|
||||
}
|
||||
|
||||
const Section: React.FC<SectionProps> = ({ title, tasks, colorClass, onTaskClick }) => (
|
||||
<div className="mb-6">
|
||||
<h3 className={`text-xs font-bold uppercase tracking-wider mb-2 ${colorClass}`}>{title}</h3>
|
||||
<div className="space-y-1">
|
||||
{tasks.map(t => <TaskItem key={t.id} task={t} onClick={onTaskClick} />)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const ToolbarItem = ({ icon, label, active = false, hideLabel = false, onClick }: { icon: React.ReactNode, label: string, active?: boolean, hideLabel?: boolean, onClick?: () => void }) => (
|
||||
<div
|
||||
title={label}
|
||||
onClick={onClick}
|
||||
className={`aura-toolbar-item ${hideLabel ? 'p-2' : 'px-3 py-2'} ${
|
||||
active ? 'aura-toolbar-item-active' : 'aura-toolbar-item-inactive'
|
||||
}`}>
|
||||
{icon}
|
||||
{!hideLabel && <span className="text-xs font-semibold">{label}</span>}
|
||||
</div>
|
||||
);
|
||||
|
||||
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<ChatMessage[]>([
|
||||
{ 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<HTMLDivElement>(null);
|
||||
const auraColRef = useRef<HTMLDivElement>(null);
|
||||
const actionColRef = useRef<HTMLDivElement>(null);
|
||||
const notificationsColRef = useRef<HTMLDivElement>(null);
|
||||
const infoColRef = useRef<HTMLDivElement>(null);
|
||||
const usageColRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const scrollToCol = (ref: React.RefObject<HTMLDivElement>) => {
|
||||
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 (
|
||||
<div className="min-h-screen flex items-center justify-center bg-aura-bg">
|
||||
<RefreshCw className="w-10 h-10 aura-text-accent-blue animate-spin" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
return <AuthFlow onComplete={(data) => setUser(data)} />;
|
||||
}
|
||||
|
||||
if (!subscription) {
|
||||
return <SubscriptionFlow uid={user.uid} onComplete={(data) => setSubscription(data)} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="aura-dashboard flex flex-col h-screen overflow-hidden">
|
||||
{/* Header */}
|
||||
<header className="aura-header flex-shrink-0">
|
||||
{/* Left: Logo & Title */}
|
||||
<div className="flex items-center gap-3 flex-shrink-0">
|
||||
<div className="aura-logo">
|
||||
<Zap className="text-white w-5 h-5" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<h1 className="text-lg sm:text-2xl font-bold aura-text-main tracking-tight leading-none">Aura Central</h1>
|
||||
<div className="flex items-center gap-1.5 ml-1">
|
||||
<Shield className="w-3.5 h-3.5 text-blue-500" />
|
||||
<Eye className="w-3.5 h-3.5 text-emerald-500" />
|
||||
</div>
|
||||
</div>
|
||||
<p className="hidden sm:block text-[10px] aura-text-muted font-bold uppercase tracking-wider mt-1">
|
||||
AI for Understanding, Reasoning, and Action
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Center: Toolbar */}
|
||||
<div className="hidden lg:flex aura-toolbar flex-grow justify-center max-w-3xl mx-auto">
|
||||
<ToolbarItem icon={<MessageSquare className="w-4 h-4" />} label="Chat" active />
|
||||
<ToolbarItem icon={<Layout className="w-4 h-4" />} label="Explorer" />
|
||||
<ToolbarItem icon={<Mail className="w-4 h-4" />} label="Mail" />
|
||||
<ToolbarItem icon={<Calendar className="w-4 h-4" />} label="Calendar" />
|
||||
<ToolbarItem icon={<Settings className="w-4 h-4" />} label="Settings" />
|
||||
<div className="w-[1px] h-4 aura-bg-inverse opacity-10 mx-1"></div>
|
||||
<ToolbarItem icon={<LayoutGrid className="w-4 h-4" />} label="All" />
|
||||
</div>
|
||||
|
||||
{/* Right: Profile Dropdown & Theme Toggle */}
|
||||
<div className="flex items-center gap-3 flex-shrink-0">
|
||||
{/* Mobile Apps Button */}
|
||||
<div className="lg:hidden relative">
|
||||
<button
|
||||
onClick={() => setIsAppsMenuOpen(!isAppsMenuOpen)}
|
||||
className="p-2 rounded-xl bg-aura-surface border border-aura-border aura-text-muted hover:aura-text-main transition-colors shadow-sm"
|
||||
>
|
||||
<LayoutGrid className="w-5 h-5" />
|
||||
</button>
|
||||
|
||||
<AnimatePresence>
|
||||
{isAppsMenuOpen && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 10, scale: 0.95 }}
|
||||
animate={{ opacity: 1, y: 0, scale: 1 }}
|
||||
exit={{ opacity: 0, y: 10, scale: 0.95 }}
|
||||
className="absolute right-0 top-full mt-2 w-48 bg-aura-surface border border-aura-border rounded-2xl shadow-xl z-50 p-2"
|
||||
>
|
||||
<div className="px-3 py-2 text-[10px] font-bold aura-text-muted uppercase tracking-wider">Select App</div>
|
||||
<button onClick={() => setIsAppsMenuOpen(false)} className="w-full text-left px-4 py-2.5 rounded-xl hover:bg-aura-bg transition-colors flex items-center gap-3 aura-text-main text-sm font-medium">
|
||||
<MessageSquare className="w-4 h-4 text-blue-500" /> Chat
|
||||
</button>
|
||||
<button onClick={() => setIsAppsMenuOpen(false)} className="w-full text-left px-4 py-2.5 rounded-xl hover:bg-aura-bg transition-colors flex items-center gap-3 aura-text-main text-sm font-medium">
|
||||
<Layout className="w-4 h-4 text-purple-500" /> Explorer
|
||||
</button>
|
||||
<button onClick={() => setIsAppsMenuOpen(false)} className="w-full text-left px-4 py-2.5 rounded-xl hover:bg-aura-bg transition-colors flex items-center gap-3 aura-text-main text-sm font-medium">
|
||||
<Mail className="w-4 h-4 text-emerald-500" /> Mail
|
||||
</button>
|
||||
<button onClick={() => setIsAppsMenuOpen(false)} className="w-full text-left px-4 py-2.5 rounded-xl hover:bg-aura-bg transition-colors flex items-center gap-3 aura-text-main text-sm font-medium">
|
||||
<Calendar className="w-4 h-4 text-amber-500" /> Calendar
|
||||
</button>
|
||||
<button onClick={() => setIsAppsMenuOpen(false)} className="w-full text-left px-4 py-2.5 rounded-xl hover:bg-aura-bg transition-colors flex items-center gap-3 aura-text-main text-sm font-medium">
|
||||
<Settings className="w-4 h-4 text-slate-500" /> Settings
|
||||
</button>
|
||||
<div className="h-[1px] aura-bg-inverse opacity-5 my-1"></div>
|
||||
<button onClick={() => setIsAppsMenuOpen(false)} className="w-full text-left px-4 py-2.5 rounded-xl hover:bg-aura-bg transition-colors flex items-center gap-3 aura-text-main text-sm font-medium">
|
||||
<LayoutGrid className="w-4 h-4 text-blue-600" /> All Apps
|
||||
</button>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
|
||||
{/* Profile Dropdown */}
|
||||
<div className="relative">
|
||||
<button
|
||||
onClick={() => setIsProfileDropdownOpen(!isProfileDropdownOpen)}
|
||||
className={`flex items-center gap-2 px-4 py-2 rounded-xl text-xs font-bold transition-all border border-aura-border shadow-sm ${
|
||||
viewMode === 'Professional' ? 'aura-text-accent-blue' : 'aura-text-accent-emerald'
|
||||
} bg-aura-surface hover:bg-aura-bg`}
|
||||
>
|
||||
{viewMode === 'Personal' ? <User className="w-3.5 h-3.5" /> : <Briefcase className="w-3.5 h-3.5" />}
|
||||
<span className="hidden sm:inline">{viewMode}</span>
|
||||
<ChevronDown className={`w-3 h-3 transition-transform ${isProfileDropdownOpen ? 'rotate-180' : ''}`} />
|
||||
</button>
|
||||
|
||||
<AnimatePresence>
|
||||
{isProfileDropdownOpen && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 10, scale: 0.95 }}
|
||||
animate={{ opacity: 1, y: 0, scale: 1 }}
|
||||
exit={{ opacity: 0, y: 10, scale: 0.95 }}
|
||||
className="absolute right-0 mt-2 w-48 aura-card p-2 shadow-xl z-50 border border-aura-border"
|
||||
>
|
||||
<button
|
||||
onClick={() => { setViewMode('Personal'); setIsProfileDropdownOpen(false); }}
|
||||
className={`w-full flex items-center gap-3 px-3 py-2 rounded-lg text-xs font-bold transition-colors ${
|
||||
viewMode === 'Personal' ? 'bg-aura-accent-emerald/10 text-aura-accent-emerald' : 'text-aura-text-muted hover:bg-aura-bg'
|
||||
}`}
|
||||
>
|
||||
<User className="w-4 h-4" />
|
||||
Personal Mode
|
||||
</button>
|
||||
<button
|
||||
onClick={() => { setViewMode('Professional'); setIsProfileDropdownOpen(false); }}
|
||||
className={`w-full flex items-center gap-3 px-3 py-2 rounded-lg text-xs font-bold transition-colors ${
|
||||
viewMode === 'Professional' ? 'bg-aura-accent-blue/10 text-aura-accent-blue' : 'text-aura-text-muted hover:bg-aura-bg'
|
||||
}`}
|
||||
>
|
||||
<Briefcase className="w-4 h-4" />
|
||||
Professional Mode
|
||||
</button>
|
||||
<div className="h-[1px] bg-aura-bg my-2"></div>
|
||||
<button
|
||||
onClick={() => { localStorage.removeItem('aura_uid'); setUser(null); setSubscription(null); }}
|
||||
className="w-full flex items-center gap-3 px-3 py-2 rounded-lg text-xs font-bold aura-text-accent-red hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors"
|
||||
>
|
||||
<LogOut className="w-4 h-4" />
|
||||
Log Out
|
||||
</button>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={toggleTheme}
|
||||
className="aura-btn-secondary p-2.5 rounded-xl flex items-center justify-center"
|
||||
aria-label="Toggle theme"
|
||||
>
|
||||
{theme === 'light' ? <Moon className="w-5 h-5" /> : <Sun className="w-5 h-5" />}
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Main Grid */}
|
||||
<div className="aura-main-grid flex-grow">
|
||||
|
||||
{/* Middle Column: Ask Aura - First in JSX for mobile swipeability */}
|
||||
<div ref={auraColRef} className="aura-col-aura p-6 xl:order-2">
|
||||
<div className="aura-gradient-top-bar"></div>
|
||||
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div className="relative">
|
||||
<button
|
||||
onClick={() => setIsMenuOpen(!isMenuOpen)}
|
||||
className="xl:hidden p-1.5 rounded-lg hover:bg-aura-surface transition-colors aura-text-main"
|
||||
>
|
||||
<Menu className="w-5 h-5" />
|
||||
</button>
|
||||
|
||||
|
||||
<AnimatePresence>
|
||||
{isMenuOpen && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 10, scale: 0.95 }}
|
||||
animate={{ opacity: 1, y: 0, scale: 1 }}
|
||||
exit={{ opacity: 0, y: 10, scale: 0.95 }}
|
||||
className="absolute left-0 top-full mt-2 w-48 bg-aura-surface border border-aura-border rounded-2xl shadow-xl z-50 p-2"
|
||||
>
|
||||
<button
|
||||
onClick={() => scrollToCol(auraColRef)}
|
||||
className="w-full text-left px-4 py-2.5 rounded-xl hover:bg-aura-bg transition-colors flex items-center gap-3 aura-text-main text-sm font-medium"
|
||||
>
|
||||
<Brain className="w-4 h-4 text-purple-500" /> Ask Aura
|
||||
</button>
|
||||
<button
|
||||
onClick={() => scrollToCol(actionColRef)}
|
||||
className="w-full text-left px-4 py-2.5 rounded-xl hover:bg-aura-bg transition-colors flex items-center gap-3 aura-text-main text-sm font-medium"
|
||||
>
|
||||
<LayoutGrid className="w-4 h-4 text-blue-500" /> Actionables
|
||||
</button>
|
||||
<button
|
||||
onClick={() => scrollToCol(notificationsColRef)}
|
||||
className="w-full text-left px-4 py-2.5 rounded-xl hover:bg-aura-bg transition-colors flex items-center gap-3 aura-text-main text-sm font-medium"
|
||||
>
|
||||
<Bell className="w-4 h-4 text-indigo-500" /> Notifications
|
||||
</button>
|
||||
<button
|
||||
onClick={() => scrollToCol(infoColRef)}
|
||||
className="w-full text-left px-4 py-2.5 rounded-xl hover:bg-aura-bg transition-colors flex items-center gap-3 aura-text-main text-sm font-medium"
|
||||
>
|
||||
<Zap className="w-4 h-4 text-amber-500" /> Information
|
||||
</button>
|
||||
<button
|
||||
onClick={() => scrollToCol(usageColRef)}
|
||||
className="w-full text-left px-4 py-2.5 rounded-xl hover:bg-aura-bg transition-colors flex items-center gap-3 aura-text-main text-sm font-medium"
|
||||
>
|
||||
<BarChart3 className="w-4 h-4 text-emerald-500" /> Usage Stats
|
||||
</button>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
<h2 className="text-lg font-semibold aura-text-main">Ask Aura</h2>
|
||||
<div className="flex gap-1 aura-toolbar p-1">
|
||||
<button
|
||||
title="Aura Chat"
|
||||
onClick={() => setAuraMode('chat')}
|
||||
className={`p-1.5 rounded-xl transition-all ${auraMode === 'chat' ? 'bg-aura-surface shadow-sm text-aura-accent-blue' : 'text-aura-text-muted hover:text-aura-text'}`}
|
||||
>
|
||||
<MessageSquare className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
title="Aura Voice"
|
||||
onClick={() => setAuraMode('voice')}
|
||||
className={`p-1.5 rounded-xl transition-all ${auraMode === 'voice' ? 'bg-aura-surface shadow-sm text-aura-accent-amber' : 'text-aura-text-muted hover:text-aura-text'}`}
|
||||
>
|
||||
<Mic className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
title="Aura Meet"
|
||||
onClick={() => setAuraMode('meet')}
|
||||
className={`p-1.5 rounded-xl transition-all ${auraMode === 'meet' ? 'bg-aura-surface shadow-sm text-aura-accent-red' : 'text-aura-text-muted hover:text-aura-text'}`}
|
||||
>
|
||||
<Video className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<AnimatePresence mode="wait">
|
||||
{auraMode === 'chat' ? (
|
||||
<motion.div
|
||||
key="chat-mode"
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
exit={{ opacity: 0, x: 20 }}
|
||||
className="flex flex-col h-full"
|
||||
>
|
||||
|
||||
<div className="flex gap-2 mb-6 overflow-x-auto pb-2 no-scrollbar">
|
||||
<button className="aura-suggestion-pill-blue">
|
||||
<Zap className="w-3 h-3" /> Summarize Tasks
|
||||
</button>
|
||||
<button className="aura-suggestion-pill-orange">
|
||||
<Bell className="w-3 h-3" /> Set a Reminder
|
||||
</button>
|
||||
<button className="aura-suggestion-pill-indigo">
|
||||
<Timer className="w-3 h-3" /> Start a Timer
|
||||
</button>
|
||||
<button className="aura-suggestion-pill-red" onClick={() => setAuraMode('meet')}>
|
||||
<Video className="w-3 h-3" /> Aura Meet
|
||||
</button>
|
||||
<button
|
||||
className="flex items-center gap-2 px-3 py-1.5 rounded-full border text-xs font-bold whitespace-nowrap transition-colors bg-aura-surface border-aura-border aura-text-main hover:bg-aura-bg"
|
||||
onClick={() => setMessages(prev => [...prev, { role: 'model', text: "I am Aura Central — AI for Understanding, Reasoning, and Action. I can help you with:\n\n• **Understanding**: Summarizing tasks, analyzing your schedule, and organizing files.\n• **Reasoning**: Prioritizing your day, identifying bottlenecks, and strategic planning.\n• **Action**: Setting reminders, starting timers, and real-time meeting assistance.\n\nHow can I assist you today?" }])}
|
||||
>
|
||||
<Brain className="w-3 h-3 text-purple-500" /> What can I do?
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex-grow overflow-y-auto mb-4 space-y-4 pr-2">
|
||||
<AnimatePresence initial={false}>
|
||||
{messages.map((msg, i) => (
|
||||
<motion.div
|
||||
key={i}
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className={`aura-message-container flex flex-col ${msg.role === 'user' ? 'items-end' : 'items-start'}`}
|
||||
>
|
||||
<div className={msg.role === 'user' ? 'aura-chat-bubble-user' : 'aura-chat-bubble-model'}>
|
||||
<div className="markdown-body">
|
||||
<ReactMarkdown>{msg.text}</ReactMarkdown>
|
||||
</div>
|
||||
</div>
|
||||
{msg.role === 'model' && (
|
||||
<div className="aura-message-actions">
|
||||
<button
|
||||
onClick={() => navigator.clipboard.writeText(msg.text)}
|
||||
className="p-1 rounded-md hover:bg-aura-surface text-aura-text-muted transition-colors"
|
||||
title="Copy"
|
||||
>
|
||||
<Copy className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
<button
|
||||
className="p-1 rounded-md hover:bg-aura-surface text-aura-text-muted transition-colors"
|
||||
title="Retry"
|
||||
>
|
||||
<RefreshCw className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</motion.div>
|
||||
))}
|
||||
{isTyping && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
className="flex justify-start"
|
||||
>
|
||||
<div className="aura-chat-bubble-model">
|
||||
<div className="flex gap-1">
|
||||
<div className="w-1.5 h-1.5 aura-text-muted rounded-full animate-bounce"></div>
|
||||
<div className="w-1.5 h-1.5 aura-text-muted rounded-full animate-bounce [animation-delay:0.2s]"></div>
|
||||
<div className="w-1.5 h-1.5 aura-text-muted rounded-full animate-bounce [animation-delay:0.4s]"></div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
<div ref={chatEndRef} />
|
||||
</div>
|
||||
|
||||
<div className="relative">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Prompt..."
|
||||
value={chatInput}
|
||||
onChange={(e) => setChatInput(e.target.value)}
|
||||
onKeyDown={(e) => e.key === 'Enter' && handleSendMessage()}
|
||||
className="aura-input pr-24 text-sm"
|
||||
/>
|
||||
<div className="absolute right-2 top-1/2 -translate-y-1/2 flex items-center gap-2">
|
||||
<Smile className="w-5 h-5 aura-text-muted cursor-pointer hover:aura-text-main" />
|
||||
<button
|
||||
onClick={handleSendMessage}
|
||||
className="w-8 h-8 rounded-lg bg-blue-500 flex items-center justify-center text-white hover:bg-blue-600 transition-colors"
|
||||
>
|
||||
<Send className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
) : auraMode === 'voice' ? (
|
||||
<motion.div
|
||||
key="voice-mode"
|
||||
initial={{ opacity: 0, scale: 0.95 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0, scale: 0.95 }}
|
||||
className="flex flex-col h-full items-center justify-center text-center p-4"
|
||||
>
|
||||
<div className="mb-8 relative">
|
||||
<div className={`w-40 h-40 rounded-full flex items-center justify-center transition-all duration-500 ${isRecording ? 'bg-amber-500/10' : 'bg-aura-surface border border-aura-border'}`}>
|
||||
<div className={`w-24 h-24 rounded-full flex items-center justify-center shadow-xl transition-all duration-300 ${isRecording ? 'bg-amber-500 scale-110 shadow-amber-500/40' : 'aura-bg-accent-blue shadow-blue-500/40'}`}>
|
||||
<Mic className="w-10 h-10 text-white" />
|
||||
</div>
|
||||
</div>
|
||||
{isRecording && (
|
||||
<div className="absolute -inset-8">
|
||||
<motion.div
|
||||
animate={{ scale: [1, 1.8], opacity: [0.3, 0] }}
|
||||
transition={{ repeat: Infinity, duration: 1.5 }}
|
||||
className="w-full h-full rounded-full border-2 border-amber-500/30"
|
||||
/>
|
||||
<motion.div
|
||||
animate={{ scale: [1, 1.4], opacity: [0.5, 0] }}
|
||||
transition={{ repeat: Infinity, duration: 1.5, delay: 0.5 }}
|
||||
className="w-full h-full rounded-full border-2 border-amber-500/20"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<h3 className="text-2xl font-bold aura-text-main mb-2">Aura Voice</h3>
|
||||
<p className="aura-text-muted text-sm mb-12 max-w-xs">
|
||||
{isRecording ? 'Aura is listening...' : 'Tap to start a voice conversation with Aura.'}
|
||||
</p>
|
||||
|
||||
<button
|
||||
onClick={() => setIsRecording(!isRecording)}
|
||||
className={`px-10 py-4 rounded-2xl font-bold text-white shadow-xl transition-all active:scale-95 ${isRecording ? 'bg-red-500 hover:bg-red-600' : 'aura-bg-accent-blue hover:aura-bg-accent-blue/90'}`}
|
||||
>
|
||||
{isRecording ? 'Stop Listening' : 'Start Talking'}
|
||||
</button>
|
||||
</motion.div>
|
||||
) : (
|
||||
<motion.div
|
||||
key="meet-mode"
|
||||
initial={{ opacity: 0, x: 20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
exit={{ opacity: 0, x: -20 }}
|
||||
className="flex flex-col h-full items-center justify-center text-center"
|
||||
>
|
||||
<div className="mb-8 relative">
|
||||
<div className={`w-32 h-32 rounded-full flex items-center justify-center transition-all duration-500 ${isRecording ? 'bg-red-500/10 scale-110' : 'bg-aura-surface border border-aura-border'}`}>
|
||||
{isRecording ? (
|
||||
<motion.div
|
||||
animate={{ scale: [1, 1.2, 1] }}
|
||||
transition={{ repeat: Infinity, duration: 1.5 }}
|
||||
className="w-16 h-16 rounded-full bg-red-500 flex items-center justify-center shadow-lg shadow-red-500/40"
|
||||
>
|
||||
<Square className="w-6 h-6 text-white fill-white" />
|
||||
</motion.div>
|
||||
) : (
|
||||
<div className="w-16 h-16 rounded-full aura-bg-accent-blue flex items-center justify-center shadow-lg shadow-blue-500/40">
|
||||
<Video className="w-6 h-6 text-white" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{isRecording && (
|
||||
<div className="absolute -inset-4">
|
||||
<motion.div
|
||||
animate={{ scale: [1, 1.5], opacity: [0.5, 0] }}
|
||||
transition={{ repeat: Infinity, duration: 2 }}
|
||||
className="w-full h-full rounded-full border-2 border-red-500/30"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<h3 className="text-xl font-bold aura-text-main mb-2">
|
||||
{isRecording ? 'Recording Meeting...' : 'Aura Meet'}
|
||||
</h3>
|
||||
<p className="aura-text-muted text-sm mb-8 max-w-xs">
|
||||
{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.'}
|
||||
</p>
|
||||
|
||||
<div className="text-4xl font-mono font-bold aura-text-main mb-12 tracking-wider">
|
||||
{formatTime(recordingTime)}
|
||||
</div>
|
||||
|
||||
<div className="flex gap-6 items-center">
|
||||
<button
|
||||
onClick={() => { setIsRecording(false); setRecordingTime(0); }}
|
||||
className="p-4 rounded-full bg-aura-surface border border-aura-border text-aura-text-muted hover:text-aura-accent-red transition-colors"
|
||||
title="Cancel"
|
||||
>
|
||||
<X className="w-6 h-6" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setIsRecording(!isRecording)}
|
||||
className={`w-20 h-20 rounded-full flex items-center justify-center shadow-xl transition-all active:scale-95 ${isRecording ? 'bg-red-500 hover:bg-red-600' : 'aura-bg-accent-blue hover:aura-bg-accent-blue/90'}`}
|
||||
>
|
||||
{isRecording ? <Square className="w-8 h-8 text-white fill-white" /> : <Play className="w-8 h-8 text-white fill-white ml-1" />}
|
||||
</button>
|
||||
<button
|
||||
className="p-4 rounded-full bg-aura-surface border border-aura-border text-aura-text-muted hover:text-aura-accent-blue transition-colors"
|
||||
title="Video Mode"
|
||||
>
|
||||
<Video className="w-6 h-6" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{isRecording && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="mt-12 w-full p-4 bg-aura-surface border border-aura-border rounded-2xl text-left"
|
||||
>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<div className="w-2 h-2 rounded-full bg-red-500 animate-pulse"></div>
|
||||
<span className="text-[10px] font-bold uppercase tracking-wider aura-text-muted">Live Transcript</span>
|
||||
</div>
|
||||
<p className="text-xs aura-text-main italic line-clamp-2">
|
||||
"So the main goal for Q2 is to increase user retention by 15% through the new loyalty program..."
|
||||
</p>
|
||||
</motion.div>
|
||||
)}
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
|
||||
{/* Left Column Group: Actionables & Projects */}
|
||||
<div className="contents xl:flex xl:flex-col xl:col-span-2 xl:order-1 gap-4 h-full overflow-hidden">
|
||||
{/* Card 1: Actionables */}
|
||||
<div ref={actionColRef} className="aura-col-action p-6 xl:h-full">
|
||||
<div className="flex items-center justify-between mb-6 flex-shrink-0">
|
||||
<div className="relative">
|
||||
<button
|
||||
onClick={() => setIsMenuOpen(!isMenuOpen)}
|
||||
className="xl:hidden p-1.5 rounded-lg hover:bg-aura-surface transition-colors aura-text-main"
|
||||
>
|
||||
<Menu className="w-5 h-5" />
|
||||
</button>
|
||||
|
||||
<AnimatePresence>
|
||||
{isMenuOpen && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 10, scale: 0.95 }}
|
||||
animate={{ opacity: 1, y: 0, scale: 1 }}
|
||||
exit={{ opacity: 0, y: 10, scale: 0.95 }}
|
||||
className="absolute left-0 top-full mt-2 w-48 bg-aura-surface border border-aura-border rounded-2xl shadow-xl z-50 p-2"
|
||||
>
|
||||
<button
|
||||
onClick={() => scrollToCol(auraColRef)}
|
||||
className="w-full text-left px-4 py-2.5 rounded-xl hover:bg-aura-bg transition-colors flex items-center gap-3 aura-text-main text-sm font-medium"
|
||||
>
|
||||
<Brain className="w-4 h-4 text-purple-500" /> Ask Aura
|
||||
</button>
|
||||
<button
|
||||
onClick={() => scrollToCol(actionColRef)}
|
||||
className="w-full text-left px-4 py-2.5 rounded-xl hover:bg-aura-bg transition-colors flex items-center gap-3 aura-text-main text-sm font-medium"
|
||||
>
|
||||
<LayoutGrid className="w-4 h-4 text-blue-500" /> Actionables
|
||||
</button>
|
||||
<button
|
||||
onClick={() => scrollToCol(notificationsColRef)}
|
||||
className="w-full text-left px-4 py-2.5 rounded-xl hover:bg-aura-bg transition-colors flex items-center gap-3 aura-text-main text-sm font-medium"
|
||||
>
|
||||
<Bell className="w-4 h-4 text-indigo-500" /> Notifications
|
||||
</button>
|
||||
<button
|
||||
onClick={() => scrollToCol(infoColRef)}
|
||||
className="w-full text-left px-4 py-2.5 rounded-xl hover:bg-aura-bg transition-colors flex items-center gap-3 aura-text-main text-sm font-medium"
|
||||
>
|
||||
<Zap className="w-4 h-4 text-amber-500" /> Information
|
||||
</button>
|
||||
<button
|
||||
onClick={() => scrollToCol(usageColRef)}
|
||||
className="w-full text-left px-4 py-2.5 rounded-xl hover:bg-aura-bg transition-colors flex items-center gap-3 aura-text-main text-sm font-medium"
|
||||
>
|
||||
<BarChart3 className="w-4 h-4 text-emerald-500" /> Usage Stats
|
||||
</button>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
<h2 className="text-lg font-semibold aura-text-main">
|
||||
{viewMode === 'Professional' ? 'Actionables' : 'Personal Hub'}
|
||||
</h2>
|
||||
<div className={`w-8 h-[2px] rounded-full ${viewMode === 'Professional' ? 'aura-bg-accent-blue' : 'aura-bg-accent-emerald'}`}></div>
|
||||
</div>
|
||||
|
||||
<div className="flex-grow overflow-y-auto pr-2 space-y-8 no-scrollbar">
|
||||
<div>
|
||||
<Section
|
||||
title="Urgent"
|
||||
tasks={tasks.filter(t => t.category === 'Urgent' && t.type === viewMode)}
|
||||
colorClass="aura-text-accent-red"
|
||||
onTaskClick={(t) => handleActionClick(t, 'task')}
|
||||
/>
|
||||
<Section
|
||||
title="Today"
|
||||
tasks={tasks.filter(t => t.category === 'Today' && t.type === viewMode)}
|
||||
colorClass="aura-text-accent-blue"
|
||||
onTaskClick={(t) => handleActionClick(t, 'task')}
|
||||
/>
|
||||
<Section
|
||||
title="Upcoming"
|
||||
tasks={tasks.filter(t => t.category === 'Upcoming' && t.type === viewMode)}
|
||||
colorClass="aura-text-accent-emerald"
|
||||
onTaskClick={(t) => handleActionClick(t, 'task')}
|
||||
/>
|
||||
<Section
|
||||
title="Waiting On"
|
||||
tasks={tasks.filter(t => t.category === 'Waiting On' && t.type === viewMode)}
|
||||
colorClass="aura-text-accent-amber"
|
||||
onTaskClick={(t) => handleActionClick(t, 'task')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right Column Group: Information & Usage Stats */}
|
||||
<div className="contents xl:flex xl:flex-col xl:col-span-3 xl:order-3 gap-4 h-full overflow-hidden">
|
||||
{/* Card 4: Information */}
|
||||
<div ref={infoColRef} className="aura-col-info p-6 xl:h-full">
|
||||
<div className="flex items-center justify-between xl:hidden mb-6 flex-shrink-0">
|
||||
<div className="relative">
|
||||
<button
|
||||
onClick={() => setIsMenuOpen(!isMenuOpen)}
|
||||
className="xl:hidden p-1.5 rounded-lg hover:bg-aura-surface transition-colors aura-text-main"
|
||||
>
|
||||
<Menu className="w-5 h-5" />
|
||||
</button>
|
||||
|
||||
<AnimatePresence>
|
||||
{isMenuOpen && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 10, scale: 0.95 }}
|
||||
animate={{ opacity: 1, y: 0, scale: 1 }}
|
||||
exit={{ opacity: 0, y: 10, scale: 0.95 }}
|
||||
className="absolute left-0 top-full mt-2 w-48 bg-aura-surface border border-aura-border rounded-2xl shadow-xl z-50 p-2"
|
||||
>
|
||||
<button
|
||||
onClick={() => scrollToCol(auraColRef)}
|
||||
className="w-full text-left px-4 py-2.5 rounded-xl hover:bg-aura-bg transition-colors flex items-center gap-3 aura-text-main text-sm font-medium"
|
||||
>
|
||||
<Brain className="w-4 h-4 text-purple-500" /> Ask Aura
|
||||
</button>
|
||||
<button
|
||||
onClick={() => scrollToCol(actionColRef)}
|
||||
className="w-full text-left px-4 py-2.5 rounded-xl hover:bg-aura-bg transition-colors flex items-center gap-3 aura-text-main text-sm font-medium"
|
||||
>
|
||||
<LayoutGrid className="w-4 h-4 text-blue-500" /> Actionables
|
||||
</button>
|
||||
<button
|
||||
onClick={() => scrollToCol(notificationsColRef)}
|
||||
className="w-full text-left px-4 py-2.5 rounded-xl hover:bg-aura-bg transition-colors flex items-center gap-3 aura-text-main text-sm font-medium"
|
||||
>
|
||||
<Bell className="w-4 h-4 text-indigo-500" /> Notifications
|
||||
</button>
|
||||
<button
|
||||
onClick={() => scrollToCol(infoColRef)}
|
||||
className="w-full text-left px-4 py-2.5 rounded-xl hover:bg-aura-bg transition-colors flex items-center gap-3 aura-text-main text-sm font-medium"
|
||||
>
|
||||
<Zap className="w-4 h-4 text-amber-500" /> Information
|
||||
</button>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex-grow flex flex-col">
|
||||
<AnimatePresence mode="wait">
|
||||
{!expandedTile ? (
|
||||
<motion.div
|
||||
key="grid"
|
||||
initial={{ opacity: 0, scale: 0.95 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0, scale: 0.95 }}
|
||||
className="grid grid-cols-2 gap-3 w-full"
|
||||
>
|
||||
{/* Notifications Tile - Spans 2 columns */}
|
||||
<motion.div
|
||||
ref={notificationsColRef}
|
||||
layout
|
||||
className="col-span-2 p-4 rounded-3xl bg-aura-surface border border-aura-border flex flex-col transition-all overflow-hidden"
|
||||
>
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Bell className="w-4 h-4 text-indigo-500" />
|
||||
<span className="text-[10px] font-bold uppercase tracking-wider aura-text-main">Notifications</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setExpandedTile('notifications')}
|
||||
className="text-[10px] font-bold aura-text-muted hover:aura-text-main transition-colors"
|
||||
>
|
||||
View All
|
||||
</button>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
{NOTIFICATIONS.slice(0, 2).map((n) => (
|
||||
<motion.div
|
||||
layout
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
key={n.id}
|
||||
onClick={() => 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"
|
||||
>
|
||||
<div className="w-6 h-6 rounded-lg bg-aura-surface flex items-center justify-center shadow-sm flex-shrink-0">
|
||||
{n.icon}
|
||||
</div>
|
||||
<div className="flex-grow min-w-0">
|
||||
<p className="text-[10px] font-bold aura-text-main truncate">{n.title}</p>
|
||||
<p className="text-[8px] aura-text-muted">{n.time}</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
{/* Projects Cube */}
|
||||
<motion.div
|
||||
whileHover={{ scale: 1.02 }}
|
||||
onClick={() => 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"
|
||||
>
|
||||
<div className="w-12 h-12 rounded-2xl bg-purple-500/10 flex items-center justify-center mb-2 group-hover:bg-purple-500/20 transition-colors">
|
||||
<Briefcase className="w-6 h-6 text-purple-500" />
|
||||
</div>
|
||||
<span className="text-xl font-black aura-text-main leading-none">03</span>
|
||||
<span className="text-[10px] aura-text-muted mt-1 font-bold uppercase tracking-wider">Projects</span>
|
||||
</motion.div>
|
||||
|
||||
{/* Schedule Cube */}
|
||||
<motion.div
|
||||
whileHover={{ scale: 1.02 }}
|
||||
onClick={() => 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"
|
||||
>
|
||||
<div className="w-12 h-12 rounded-2xl bg-blue-500/10 flex items-center justify-center mb-2 group-hover:bg-blue-500/20 transition-colors">
|
||||
<Calendar className="w-6 h-6 text-blue-500" />
|
||||
</div>
|
||||
<span className="text-xl font-black aura-text-main leading-none">2 PM</span>
|
||||
<span className="text-[10px] aura-text-muted mt-1 font-bold uppercase tracking-wider">Schedule</span>
|
||||
</motion.div>
|
||||
|
||||
{/* File Cube */}
|
||||
<motion.div
|
||||
whileHover={{ scale: 1.02 }}
|
||||
onClick={() => 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"
|
||||
>
|
||||
<div className="w-12 h-12 rounded-2xl bg-emerald-500/10 flex items-center justify-center mb-2 group-hover:bg-emerald-500/20 transition-colors">
|
||||
<Folder className="w-6 h-6 text-emerald-500" />
|
||||
</div>
|
||||
<span className="text-xl font-black aura-text-main leading-none">12</span>
|
||||
<span className="text-[10px] aura-text-muted mt-1 font-bold uppercase tracking-wider">Files</span>
|
||||
</motion.div>
|
||||
|
||||
{/* People Cube */}
|
||||
<motion.div
|
||||
whileHover={{ scale: 1.02 }}
|
||||
onClick={() => 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"
|
||||
>
|
||||
<div className="w-12 h-12 rounded-2xl bg-amber-500/10 flex items-center justify-center mb-2 group-hover:bg-amber-500/20 transition-colors">
|
||||
<User className="w-6 h-6 text-amber-500" />
|
||||
</div>
|
||||
<span className="text-xl font-black aura-text-main leading-none">05</span>
|
||||
<span className="text-[10px] aura-text-muted mt-1 font-bold uppercase tracking-wider">People</span>
|
||||
</motion.div>
|
||||
|
||||
{/* Billing Cube */}
|
||||
<motion.div
|
||||
whileHover={{ scale: 1.02 }}
|
||||
onClick={() => 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"
|
||||
>
|
||||
<div className="w-12 h-12 rounded-2xl bg-indigo-500/10 flex items-center justify-center mb-2 group-hover:bg-indigo-500/20 transition-colors">
|
||||
<Coins className="w-6 h-6 text-indigo-500" />
|
||||
</div>
|
||||
<span className="text-xl font-black aura-text-main leading-none">$42</span>
|
||||
<span className="text-[10px] aura-text-muted mt-1 font-bold uppercase tracking-wider">Billing</span>
|
||||
</motion.div>
|
||||
|
||||
{/* Health Cube */}
|
||||
<motion.div
|
||||
whileHover={{ scale: 1.02 }}
|
||||
onClick={() => 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"
|
||||
>
|
||||
<div className="w-12 h-12 rounded-2xl bg-rose-500/10 flex items-center justify-center mb-2 group-hover:bg-rose-500/20 transition-colors">
|
||||
<Target className="w-6 h-6 text-rose-500" />
|
||||
</div>
|
||||
<span className="text-xl font-black aura-text-main leading-none">98%</span>
|
||||
<span className="text-[10px] aura-text-muted mt-1 font-bold uppercase tracking-wider">Health</span>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
) : (
|
||||
<motion.div
|
||||
key="expanded"
|
||||
initial={{ opacity: 0, x: 20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
exit={{ opacity: 0, x: -20 }}
|
||||
className="flex flex-col h-full"
|
||||
>
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<button
|
||||
onClick={() => setExpandedTile(null)}
|
||||
className="p-1.5 rounded-lg hover:bg-aura-surface aura-text-muted hover:aura-text-main transition-colors"
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
</button>
|
||||
<span className="text-sm font-bold aura-text-main capitalize">{expandedTile}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex-grow overflow-y-auto no-scrollbar space-y-4">
|
||||
{expandedTile === 'notifications' && (
|
||||
<div className="space-y-3">
|
||||
{NOTIFICATIONS.map((n) => (
|
||||
<motion.div
|
||||
layout
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
key={n.id}
|
||||
onClick={() => handleActionClick(n, 'notification')}
|
||||
className="aura-row-card p-3 group hover:bg-aura-bg transition-colors cursor-pointer"
|
||||
>
|
||||
<div className="w-8 h-8 rounded-lg bg-aura-surface flex items-center justify-center shadow-sm">
|
||||
{n.icon}
|
||||
</div>
|
||||
<div className="flex-grow min-w-0">
|
||||
<p className="text-xs font-bold aura-text-main truncate">{n.title}</p>
|
||||
<p className="text-[10px] aura-text-muted">{n.time}</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{expandedTile === 'projects' && (
|
||||
<div className="space-y-4">
|
||||
{[
|
||||
{ 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) => (
|
||||
<div key={i} className="cursor-pointer group" onClick={() => handleActionClick(p, 'project')}>
|
||||
<div className="flex justify-between text-[10px] mb-1">
|
||||
<span className="font-medium aura-text-muted group-hover:aura-text-main transition-colors">{p.name}</span>
|
||||
<span className="aura-text-muted">{p.progress}%</span>
|
||||
</div>
|
||||
<div className="w-full h-1 aura-progress-track">
|
||||
<motion.div
|
||||
initial={{ width: 0 }}
|
||||
animate={{ width: `${p.progress}%` }}
|
||||
className={`h-full ${p.color}`}
|
||||
></motion.div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{expandedTile === 'schedule' && (
|
||||
<div className="space-y-3">
|
||||
{SCHEDULE.filter(item => item.type === viewMode).map((item, i) => (
|
||||
<div key={i} className="flex items-center gap-4 group cursor-pointer" onClick={() => handleActionClick(item, 'event')}>
|
||||
<div className="text-xs font-bold aura-text-muted w-16">{item.time}</div>
|
||||
<div className="flex-grow aura-row-card group-hover:bg-slate-100 dark:group-hover:bg-slate-800 transition-colors">
|
||||
{item.icon}
|
||||
<span className="text-sm font-medium aura-text-main">{item.title}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{expandedTile === 'files' && (
|
||||
<div className="space-y-1">
|
||||
{RECENT_FILES.filter(file => file.viewType === viewMode).map((file, i) => (
|
||||
<div key={i} className="item-row group py-1.5 cursor-pointer" onClick={() => handleActionClick(file, 'file')}>
|
||||
<div className="w-6 h-6 rounded-lg bg-slate-100 dark:bg-slate-800 flex items-center justify-center">
|
||||
{file.icon}
|
||||
</div>
|
||||
<span className="text-xs aura-text-main flex-grow">{file.name}</span>
|
||||
<ChevronRight className="w-3 h-3 aura-text-muted group-hover:aura-text-main" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{expandedTile === 'people' && (
|
||||
<div className="space-y-2">
|
||||
{RECENT_CHATS.map((chat, i) => (
|
||||
<div key={i} className="aura-row-card py-2">
|
||||
<div className="w-8 h-8 rounded-full bg-aura-surface flex items-center justify-center border border-aura-border">
|
||||
{chat.icon}
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<span className="text-xs font-bold aura-text-main">{chat.name}</span>
|
||||
<span className="text-[10px] aura-text-muted">{chat.lastMsg}</span>
|
||||
</div>
|
||||
<div className="ml-auto w-2 h-2 rounded-full bg-emerald-500"></div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{expandedTile === 'billing' && (
|
||||
<div className="space-y-4">
|
||||
<div className="p-3 rounded-2xl bg-aura-bg border border-aura-border">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="text-[10px] aura-text-muted font-bold uppercase">Plan</span>
|
||||
<span className="text-xs font-bold aura-text-main">{subscription?.plan}</span>
|
||||
</div>
|
||||
<div className="w-full h-1.5 bg-aura-surface rounded-full overflow-hidden">
|
||||
<div className="h-full bg-indigo-500" style={{ width: '100%' }}></div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div className="p-2 rounded-xl bg-aura-surface border border-aura-border">
|
||||
<span className="text-[8px] aura-text-muted uppercase font-bold block">Credits</span>
|
||||
<span className="text-xs font-bold aura-text-main">100%</span>
|
||||
</div>
|
||||
<div className="p-2 rounded-xl bg-aura-surface border border-aura-border">
|
||||
<span className="text-[8px] aura-text-muted uppercase font-bold block">Storage</span>
|
||||
<span className="text-xs font-bold aura-text-main">42%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{expandedTile === 'health' && (
|
||||
<div className="space-y-3">
|
||||
<div className="p-3 rounded-2xl bg-aura-bg border border-aura-border flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 rounded-full bg-rose-500/10 flex items-center justify-center">
|
||||
<Target className="w-4 h-4 text-rose-500" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs font-bold aura-text-main">System Health</p>
|
||||
<p className="text-[10px] aura-text-muted">All systems operational</p>
|
||||
</div>
|
||||
</div>
|
||||
<span className="text-xs font-bold text-emerald-500">98%</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
1129
src/components/AuthFlow.tsx
Normal file
1129
src/components/AuthFlow.tsx
Normal file
@@ -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: <Shield className="w-4 h-4 text-blue-600 dark:text-blue-400" />,
|
||||
color: "blue"
|
||||
},
|
||||
{
|
||||
title: "Peak Productivity",
|
||||
description: "Streamline your workflow with intelligent automation.",
|
||||
icon: <Zap className="w-4 h-4 text-orange-600 dark:text-orange-400" />,
|
||||
color: "orange"
|
||||
},
|
||||
{
|
||||
title: "Autonomous Assistant",
|
||||
description: "Works across WhatsApp, Web, and Voice, always synchronized.",
|
||||
icon: <Brain className="w-4 h-4 text-blue-500" />,
|
||||
color: "blue"
|
||||
},
|
||||
{
|
||||
title: "Agent Library",
|
||||
description: "Growing collection of specialized agents for any task.",
|
||||
icon: <Library className="w-4 h-4 text-indigo-600 dark:text-indigo-400" />,
|
||||
color: "indigo"
|
||||
},
|
||||
{
|
||||
title: "Social Impact",
|
||||
description: "Your usage funds scholarships for students in need.",
|
||||
icon: <Heart className="w-4 h-4 text-rose-600 dark:text-rose-400" />,
|
||||
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<AuthFlowProps> = ({ onComplete }) => {
|
||||
const [step, setStep] = useState<AuthStep>('WELCOME');
|
||||
const [showMobileBenefits, setShowMobileBenefits] = useState(false);
|
||||
const [showPrivacyPolicy, setShowPrivacyPolicy] = useState(false);
|
||||
const [mobileCarouselIndex, setMobileCarouselIndex] = useState(0);
|
||||
const scrollContainerRef = useRef<HTMLDivElement>(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<string | null>(null);
|
||||
const inputRef = useRef<HTMLInputElement>(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 (
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="w-full min-h-screen flex justify-center bg-aura-bg relative z-10 overflow-x-hidden"
|
||||
>
|
||||
<div className="w-full max-w-[1400px] flex flex-col md:flex-row overflow-hidden h-screen border-x border-aura-border/10 shadow-2xl bg-aura-surface/50">
|
||||
{/* Left Side: Hero */}
|
||||
<div className="w-full md:w-[45%] xl:w-[35%] flex-shrink-0 flex flex-col justify-between p-6 md:p-12 xl:p-16 relative overflow-hidden border-b md:border-b-0 md:border-r border-aura-border/20 h-full">
|
||||
{/* Background Decorative Element */}
|
||||
<div className="absolute top-[-10%] left-[-10%] w-[40%] h-[40%] bg-blue-400/10 blur-[120px] rounded-full"></div>
|
||||
<div className="absolute bottom-[-10%] right-[10%] w-[30%] h-[30%] bg-indigo-400/10 blur-[100px] rounded-full"></div>
|
||||
|
||||
<div className="relative z-10 space-y-6 md:space-y-8">
|
||||
<div className="flex flex-row items-center gap-4 md:flex-col md:items-start md:gap-8">
|
||||
<div className="aura-logo shadow-2xl shadow-blue-200 flex-shrink-0">
|
||||
<Zap className="text-white w-8 h-8 md:w-10 md:h-10" />
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<h1 className="text-4xl md:text-7xl font-bold aura-text-main tracking-tighter leading-[0.9]">
|
||||
Aura <span className="aura-text-accent-blue italic">Central</span>
|
||||
</h1>
|
||||
<span className="md:hidden block text-[10px] aura-text-muted font-medium tracking-tight mt-1">
|
||||
AI for Understanding, Reasoning, and Action.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="md:block space-y-4 md:space-y-8">
|
||||
<div className="space-y-4 md:space-y-6">
|
||||
<p className="text-base md:text-xl aura-text-muted font-medium max-w-md leading-relaxed">
|
||||
<span className="hidden md:block mb-1 md:mb-0">AI for Understanding, Reasoning, and Action.</span>
|
||||
<span className="italic">Your Lifestyle Guide in an AI world</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="p-4 rounded-2xl bg-rose-500/10 border border-rose-500/20 text-rose-600 dark:text-rose-400 text-sm font-bold flex items-center gap-3"
|
||||
>
|
||||
<AlertCircle className="w-5 h-5 flex-shrink-0" />
|
||||
{error}
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{/* Benefits Preview Card - Mobile Only */}
|
||||
<button
|
||||
onClick={() => setShowMobileBenefits(true)}
|
||||
className="md:hidden w-full h-56 rounded-[2.5rem] bg-aura-surface border border-aura-border shadow-xl p-6 flex flex-col justify-between relative overflow-hidden group active:scale-[0.98] transition-all"
|
||||
>
|
||||
<div className="absolute top-0 right-0 p-4">
|
||||
<LayoutGrid className="w-4 h-4 text-aura-accent-blue opacity-40" />
|
||||
</div>
|
||||
<AnimatePresence mode="wait">
|
||||
<motion.div
|
||||
key={currentBenefitIndex}
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -10 }}
|
||||
className="h-full flex flex-col"
|
||||
>
|
||||
<div className="w-10 h-10 rounded-xl bg-aura-bg border border-aura-border flex items-center justify-center shadow-sm mb-4">
|
||||
{React.cloneElement(BENEFITS[currentBenefitIndex].icon as React.ReactElement<{ className?: string }>, { className: "w-5 h-5" })}
|
||||
</div>
|
||||
<div className="text-left space-y-1">
|
||||
<p className="text-xl font-bold aura-text-main leading-tight tracking-tight">
|
||||
{BENEFITS[currentBenefitIndex].title}
|
||||
</p>
|
||||
<p className="text-xs aura-text-muted leading-relaxed line-clamp-2">
|
||||
{BENEFITS[currentBenefitIndex].description}
|
||||
</p>
|
||||
<div className="flex gap-1 mt-3">
|
||||
{BENEFITS.map((_, i) => (
|
||||
<div key={i} className={`h-1 rounded-full transition-all duration-500 ${i === currentBenefitIndex ? 'w-6 bg-aura-accent-blue' : 'w-1.5 bg-aura-border'}`}></div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
|
||||
{/* Tap to expand hint */}
|
||||
<div className="absolute bottom-3 right-6 flex items-center gap-2 text-[9px] font-bold aura-text-accent-blue uppercase tracking-widest opacity-60">
|
||||
Explore
|
||||
<ArrowRight className="w-2.5 h-2.5" />
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<div className="pt-2 flex flex-col sm:flex-row items-center gap-4">
|
||||
<button
|
||||
onClick={() => handleNext()}
|
||||
className="w-full sm:w-auto group aura-btn-primary px-8 py-4 rounded-2xl text-lg shadow-xl hover:shadow-blue-300 dark:hover:shadow-blue-900/40 flex items-center justify-center gap-4 transition-all hover:scale-[1.02] active:scale-95"
|
||||
>
|
||||
Get Started
|
||||
<ArrowRight className="w-5 h-5 group-hover:translate-x-2 transition-transform" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* HumanizeIQ Branding */}
|
||||
<div className="pt-2 flex items-center gap-4 opacity-70 hover:opacity-100 transition-opacity">
|
||||
<div className="w-12 h-12 rounded-xl bg-aura-surface border border-aura-border flex items-center justify-center text-[8px] font-black aura-text-muted text-center leading-none p-1 uppercase tracking-tighter shadow-inner">
|
||||
Humanize<br/>IQ<br/>Logo
|
||||
</div>
|
||||
<div className="space-y-0.5">
|
||||
<p className="text-[9px] font-bold aura-text-muted uppercase tracking-[0.2em]">A Product From</p>
|
||||
<p className="text-base font-bold aura-text-main leading-none">HumanizeIQ</p>
|
||||
<p className="text-[10px] aura-text-accent-blue font-bold italic">Profit for Good (PFG) Company</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom Stats/Trust */}
|
||||
<div className="relative z-10 mt-auto pt-6 flex items-center justify-between border-t border-aura-border/50">
|
||||
<div>
|
||||
<p className="text-xl font-bold aura-text-main">99.9%</p>
|
||||
<p className="text-[9px] aura-text-muted uppercase tracking-widest font-bold">Uptime</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xl font-bold aura-text-main">256-bit</p>
|
||||
<p className="text-[9px] aura-text-muted uppercase tracking-widest font-bold">Security</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xl font-bold aura-text-main">Global</p>
|
||||
<p className="text-[9px] aura-text-muted uppercase tracking-widest font-bold">Access</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right Side: Benefit Card Cubes */}
|
||||
<div className="hidden md:flex w-full md:w-[55%] xl:w-[65%] flex-shrink-0 bg-aura-surface flex-col overflow-y-auto md:overflow-hidden no-scrollbar">
|
||||
<div className="flex-grow p-6 md:p-8 xl:p-10 overflow-y-auto md:overflow-hidden no-scrollbar">
|
||||
<div className="h-full flex flex-col space-y-6">
|
||||
<h3 className="text-xs font-bold aura-text-muted uppercase tracking-[0.2em]">Platform Benefits</h3>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3 flex-grow content-start">
|
||||
{/* Combined Benefit: Privacy & Security */}
|
||||
<div className="p-5 rounded-[1.5rem] bg-aura-bg border border-aura-border shadow-sm hover:shadow-md transition-all group flex flex-col justify-center">
|
||||
<div className="flex gap-2 mb-3">
|
||||
<div className="w-8 h-8 rounded-lg bg-blue-100 dark:bg-blue-900/30 flex items-center justify-center group-hover:scale-110 transition-transform">
|
||||
<Shield className="w-4 h-4 text-blue-600 dark:text-blue-400" />
|
||||
</div>
|
||||
<div className="w-8 h-8 rounded-lg bg-red-100 dark:bg-red-900/30 flex items-center justify-center group-hover:scale-110 transition-transform">
|
||||
<Lock className="w-4 h-4 text-red-600 dark:text-red-400" />
|
||||
</div>
|
||||
</div>
|
||||
<h4 className="text-base font-bold aura-text-main mb-1">Privacy & Security</h4>
|
||||
<p className="aura-text-muted text-[10px] leading-tight mb-2">
|
||||
Enterprise-grade encryption and advanced threat detection. Your data remains yours.
|
||||
</p>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setShowPrivacyPolicy(true);
|
||||
}}
|
||||
className="text-[9px] font-bold uppercase tracking-widest text-blue-600 dark:text-blue-400 hover:underline text-left"
|
||||
>
|
||||
View Privacy Policy
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Benefit Cube 3 */}
|
||||
<div className="p-5 rounded-[1.5rem] bg-aura-bg border border-aura-border shadow-sm hover:shadow-md transition-all group flex flex-col justify-center">
|
||||
<div className="w-8 h-8 rounded-lg bg-orange-100 dark:bg-orange-900/30 flex items-center justify-center mb-3 group-hover:scale-110 transition-transform">
|
||||
<Zap className="w-4 h-4 text-orange-600 dark:text-orange-400" />
|
||||
</div>
|
||||
<h4 className="text-base font-bold aura-text-main mb-1">Peak Productivity</h4>
|
||||
<p className="aura-text-muted text-[10px] leading-tight">
|
||||
Streamline your workflow with intelligent automation for mundane tasks.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Autonomous Assistant Cube */}
|
||||
<div className="p-5 rounded-[1.5rem] bg-aura-bg border border-aura-border shadow-sm flex flex-col justify-center">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<div className="w-8 h-8 rounded-full bg-blue-500/10 flex items-center justify-center">
|
||||
<Brain className="w-4 h-4 text-blue-500" />
|
||||
</div>
|
||||
<h4 className="text-sm font-bold aura-text-main leading-tight">Autonomous Assistant</h4>
|
||||
</div>
|
||||
<p className="aura-text-muted text-[10px] leading-tight mb-3">
|
||||
Works across WhatsApp, Web, and Voice, always synchronized.
|
||||
</p>
|
||||
<div className="flex gap-3">
|
||||
<MessageSquare className="w-3.5 h-3.5 text-emerald-500" />
|
||||
<Globe className="w-3.5 h-3.5 text-blue-500" />
|
||||
<Mic className="w-3.5 h-3.5 text-orange-500" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Agent Library */}
|
||||
<div className="p-5 rounded-[1.5rem] bg-aura-bg border border-aura-border shadow-sm hover:shadow-md transition-all group flex flex-col justify-center">
|
||||
<div className="w-8 h-8 rounded-lg bg-indigo-100 dark:bg-indigo-900/30 flex items-center justify-center mb-3 group-hover:scale-110 transition-transform">
|
||||
<Library className="w-4 h-4 text-indigo-600 dark:text-indigo-400" />
|
||||
</div>
|
||||
<h4 className="text-base font-bold aura-text-main mb-1">Agent Library</h4>
|
||||
<p className="aura-text-muted text-[10px] leading-tight">
|
||||
Growing collection of specialized agents: Presentation, Tutor, Research, and more.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Combined Benefit: Social Impact & Community - 2 Slots */}
|
||||
<div className="lg:col-span-2 p-6 rounded-[1.5rem] bg-aura-bg border border-aura-border shadow-sm hover:shadow-md transition-all group flex flex-col md:flex-row items-center gap-6">
|
||||
<div className="flex -space-x-3">
|
||||
<div className="w-12 h-12 rounded-xl bg-rose-100 dark:bg-rose-900/30 flex items-center justify-center shadow-lg group-hover:scale-110 transition-transform relative z-10">
|
||||
<Heart className="w-6 h-6 text-rose-600 dark:text-rose-400" />
|
||||
</div>
|
||||
<div className="w-12 h-12 rounded-xl bg-amber-100 dark:bg-amber-900/30 flex items-center justify-center shadow-lg group-hover:scale-110 transition-transform translate-y-2">
|
||||
<Users className="w-6 h-6 text-amber-600 dark:text-amber-400" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1 space-y-2">
|
||||
<div className="inline-block px-2 py-0.5 rounded-full bg-rose-100 dark:bg-rose-900/30 text-[9px] font-bold text-rose-600 dark:text-rose-400 uppercase tracking-widest">The Aura Difference</div>
|
||||
<h4 className="text-xl font-bold aura-text-main">Why Choose Aura?</h4>
|
||||
<p className="aura-text-muted text-[11px] leading-tight">
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Enterprise Level Plan - Aligned to Bottom */}
|
||||
<div className="p-8 pt-0">
|
||||
<div className="p-6 rounded-[2rem] bg-gradient-to-br from-blue-600 via-indigo-700 to-purple-800 text-white shadow-2xl flex flex-col md:flex-row items-center justify-between gap-6">
|
||||
<div className="space-y-3 max-w-md">
|
||||
<div>
|
||||
<div className="inline-block px-2 py-0.5 rounded-full bg-white/20 text-[9px] font-bold uppercase tracking-widest mb-1">Standard for All</div>
|
||||
<h4 className="text-2xl font-bold">Enterprise Level Plan</h4>
|
||||
<p className="text-blue-100 text-[11px]">Premium features unlocked for every consumer.</p>
|
||||
</div>
|
||||
<ul className="grid grid-cols-1 sm:grid-cols-2 gap-x-4 gap-y-1">
|
||||
<li className="flex items-center gap-2 text-[11px]"><CheckCircle2 className="w-4 h-4 text-blue-200" /> Bundled Usage</li>
|
||||
<li className="flex items-center gap-2 text-[11px]"><CheckCircle2 className="w-4 h-4 text-blue-200" /> Pay-as-you-go</li>
|
||||
<li className="flex items-center gap-2 text-[11px]"><CheckCircle2 className="w-4 h-4 text-blue-200" /> Never Expire</li>
|
||||
<li className="flex items-center gap-2 text-[11px]"><CheckCircle2 className="w-4 h-4 text-blue-200" /> Profit-Sharing</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-center md:items-end gap-3 bg-white/10 p-6 rounded-2xl border border-white/20 backdrop-blur-sm min-w-[200px]">
|
||||
<div className="text-center md:text-right">
|
||||
<span className="text-4xl font-bold">$7.99</span>
|
||||
<span className="text-base opacity-80">/mo</span>
|
||||
</div>
|
||||
<div className="text-[10px] text-blue-100 text-center md:text-right max-w-[180px] leading-tight">
|
||||
Rewards system based on company profits.
|
||||
</div>
|
||||
<button className="w-full py-3 px-6 rounded-lg bg-white text-blue-700 font-bold text-sm hover:bg-blue-50 transition-all hover:scale-[1.02] active:scale-95 shadow-lg">
|
||||
Get Enterprise Access
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mobile Benefits Overlay */}
|
||||
<AnimatePresence>
|
||||
{showMobileBenefits && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: '100%' }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: '100%' }}
|
||||
transition={{ type: 'spring', damping: 25, stiffness: 200 }}
|
||||
className="fixed inset-0 z-[100] bg-aura-bg xl:hidden flex flex-col"
|
||||
>
|
||||
{/* Content - Full screen horizontal cards */}
|
||||
<div
|
||||
ref={scrollContainerRef}
|
||||
onScroll={(e) => {
|
||||
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) => (
|
||||
<div key={idx} className="flex-shrink-0 w-full h-full snap-center flex flex-col">
|
||||
<div className="p-8 bg-aura-surface flex flex-col h-full relative overflow-hidden">
|
||||
{/* Close Button inside card */}
|
||||
<div className="absolute top-6 right-6 z-50">
|
||||
<button
|
||||
onClick={() => setShowMobileBenefits(false)}
|
||||
className="p-3 rounded-2xl bg-aura-bg/50 backdrop-blur-xl border border-aura-border aura-text-main active:scale-90 transition-all"
|
||||
>
|
||||
<X className="w-6 h-6" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Decorative Background for Card */}
|
||||
<div className="absolute top-0 right-0 w-48 h-48 bg-aura-accent-blue/10 blur-[100px] rounded-full -mr-24 -mt-24"></div>
|
||||
<div className="absolute bottom-0 left-0 w-32 h-32 bg-indigo-500/5 blur-[80px] rounded-full -ml-16 -mb-16"></div>
|
||||
|
||||
<div className="w-24 h-24 rounded-[2rem] bg-aura-bg border border-aura-border flex items-center justify-center mb-10 shadow-inner">
|
||||
{React.cloneElement(benefit.icon as React.ReactElement<{ className?: string }>, { className: "w-12 h-12 text-aura-accent-blue" })}
|
||||
</div>
|
||||
|
||||
<h4 className="text-5xl font-bold aura-text-main mb-8 leading-[0.85] tracking-tighter">{benefit.title}</h4>
|
||||
<p className="aura-text-muted text-xl leading-relaxed mb-12 flex-grow font-medium">
|
||||
{benefit.description}
|
||||
</p>
|
||||
|
||||
<div className="mt-auto space-y-8">
|
||||
{benefit.title === "Privacy & Security" && (
|
||||
<button
|
||||
onClick={() => setShowPrivacyPolicy(true)}
|
||||
className="w-full py-5 rounded-2xl bg-aura-bg border border-aura-border text-sm font-black uppercase tracking-[0.2em] text-blue-600 dark:text-blue-400 shadow-sm active:scale-95 transition-all"
|
||||
>
|
||||
Privacy Protocol
|
||||
</button>
|
||||
)}
|
||||
|
||||
{benefit.title === "Social Impact" && (
|
||||
<div className="p-6 rounded-3xl bg-rose-500/5 border border-rose-500/10 backdrop-blur-sm">
|
||||
<p className="text-xs font-black text-rose-600 dark:text-rose-400 uppercase tracking-[0.2em] mb-2">PFG Initiative</p>
|
||||
<p className="text-sm aura-text-muted leading-tight font-medium">Your usage directly funds scholarships for students in the AI era.</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* Mobile Enterprise Plan Card */}
|
||||
<div className="flex-shrink-0 w-full h-full snap-center flex flex-col">
|
||||
<div className="p-8 bg-gradient-to-br from-blue-600 via-indigo-700 to-purple-800 text-white flex flex-col h-full relative overflow-hidden">
|
||||
{/* Close Button inside card */}
|
||||
<div className="absolute top-6 right-6 z-50">
|
||||
<button
|
||||
onClick={() => setShowMobileBenefits(false)}
|
||||
className="p-3 rounded-2xl bg-white/10 backdrop-blur-xl border border-white/20 text-white active:scale-90 transition-all"
|
||||
>
|
||||
<X className="w-6 h-6" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4 mb-10">
|
||||
<div className="inline-block px-3 py-1 rounded-full bg-white/20 text-[10px] font-black uppercase tracking-[0.2em]">Standard for All</div>
|
||||
<h4 className="text-5xl font-bold leading-[0.85] tracking-tighter">Enterprise Access</h4>
|
||||
<p className="text-blue-100 text-lg font-medium">Premium features unlocked for every consumer.</p>
|
||||
</div>
|
||||
|
||||
<ul className="space-y-4 mb-10">
|
||||
<li className="flex items-center gap-4 text-base font-medium"><CheckCircle2 className="w-6 h-6 text-blue-200" /> Bundled Usage</li>
|
||||
<li className="flex items-center gap-4 text-base font-medium"><CheckCircle2 className="w-6 h-6 text-blue-200" /> Pay-as-you-go</li>
|
||||
<li className="flex items-center gap-4 text-base font-medium"><CheckCircle2 className="w-6 h-6 text-blue-200" /> Never Expire</li>
|
||||
<li className="flex items-center gap-4 text-base font-medium"><CheckCircle2 className="w-6 h-6 text-blue-200" /> Profit-Sharing</li>
|
||||
</ul>
|
||||
|
||||
<div className="mt-auto bg-white/10 p-8 rounded-[2.5rem] border border-white/20 backdrop-blur-md flex flex-col items-center gap-6">
|
||||
<div className="text-center">
|
||||
<span className="text-5xl font-bold">$7.99</span>
|
||||
<span className="text-xl opacity-80">/mo</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Final CTA in Overlay removed to focus on information */}
|
||||
|
||||
{/* Navigation Arrows - Positioned at 2/3 marker */}
|
||||
<div className="absolute top-[66%] left-4 z-50 -translate-y-1/2">
|
||||
<button
|
||||
onClick={() => {
|
||||
const totalItems = BENEFITS.length + 1;
|
||||
setMobileCarouselIndex((prev) => (prev - 1 + totalItems) % totalItems);
|
||||
}}
|
||||
className="p-4 rounded-full bg-aura-surface/60 backdrop-blur-2xl border border-aura-border text-aura-accent-blue shadow-[0_0_30px_rgba(0,0,0,0.2)] active:scale-90 transition-all"
|
||||
>
|
||||
<ChevronLeft className="w-8 h-8" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="absolute top-[66%] right-4 z-50 -translate-y-1/2">
|
||||
<button
|
||||
onClick={() => {
|
||||
const totalItems = BENEFITS.length + 1;
|
||||
setMobileCarouselIndex((prev) => (prev + 1) % totalItems);
|
||||
}}
|
||||
className="p-4 rounded-full bg-aura-surface/60 backdrop-blur-2xl border border-aura-border text-aura-accent-blue shadow-[0_0_30px_rgba(0,0,0,0.2)] active:scale-90 transition-all"
|
||||
>
|
||||
<ChevronRight className="w-8 h-8" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Navigation Dots at Bottom */}
|
||||
<div className="absolute bottom-10 left-0 right-0 z-50 flex justify-center">
|
||||
<div className="flex gap-2">
|
||||
{Array.from({ length: BENEFITS.length + 1 }).map((_, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className={`h-1.5 rounded-full transition-all duration-500 ${i === mobileCarouselIndex ? 'w-8 bg-white' : 'w-2 bg-white/30'}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
|
||||
case 'PRIVACY_POLICY':
|
||||
return null; // Handled as overlay
|
||||
|
||||
case 'COLLECT_INVITE_CODE':
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: 50 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
exit={{ opacity: 0, x: -50 }}
|
||||
className="auth-step-container"
|
||||
>
|
||||
<div className="flex items-center gap-4">
|
||||
<button
|
||||
onClick={() => setStep('WELCOME')}
|
||||
className="p-3 rounded-2xl bg-aura-surface border border-aura-border aura-text-muted hover:aura-text-main transition-all active:scale-90"
|
||||
>
|
||||
<ChevronLeft className="w-5 h-5" />
|
||||
</button>
|
||||
<div className="space-y-1">
|
||||
<span className="text-blue-600 dark:text-blue-400 font-bold text-[10px] uppercase tracking-widest">Invite Only</span>
|
||||
<h2 className="text-2xl font-bold aura-text-main">Enter your invite code</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<p className="aura-text-muted text-xs font-bold uppercase tracking-wider">AI for Understanding, Reasoning, and Action</p>
|
||||
<p className="aura-text-muted">Aura is currently in private beta. Please enter your access code to begin.</p>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleNext} className="auth-input-container">
|
||||
<KeyRound className="auth-input-icon" />
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
placeholder="AURA-XXXX"
|
||||
value={inviteCode}
|
||||
onChange={(e) => setInviteCode(e.target.value)}
|
||||
className="auth-input-field uppercase tracking-widest"
|
||||
required
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={!inviteCode || isProcessing}
|
||||
className="auth-submit-btn"
|
||||
>
|
||||
{isProcessing ? <RefreshCw className="w-5 h-5 animate-spin" /> : <ArrowRight className="w-5 h-5" />}
|
||||
</button>
|
||||
</form>
|
||||
</motion.div>
|
||||
);
|
||||
|
||||
case 'COLLECT_EMAIL':
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: 50 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
exit={{ opacity: 0, x: -50 }}
|
||||
className="auth-step-container"
|
||||
>
|
||||
<div className="flex items-center gap-4">
|
||||
<button
|
||||
onClick={() => setStep('COLLECT_INVITE_CODE')}
|
||||
className="p-3 rounded-2xl bg-aura-surface border border-aura-border aura-text-muted hover:aura-text-main transition-all active:scale-90"
|
||||
>
|
||||
<ChevronLeft className="w-5 h-5" />
|
||||
</button>
|
||||
<div className="space-y-1">
|
||||
<span className="text-blue-600 dark:text-blue-400 font-bold text-[10px] uppercase tracking-widest">Step 01</span>
|
||||
<h2 className="text-2xl font-bold aura-text-main">What's your email?</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<p className="aura-text-muted">We'll validate your invite code with this email address.</p>
|
||||
</div>
|
||||
<form onSubmit={handleNext} className="auth-input-container">
|
||||
<Mail className="auth-input-icon" />
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="email"
|
||||
placeholder="email@example.com"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
className="auth-input-field"
|
||||
required
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={!email || isProcessing}
|
||||
className="auth-submit-btn"
|
||||
>
|
||||
{isProcessing ? <RefreshCw className="w-5 h-5 animate-spin" /> : <ArrowRight className="w-5 h-5" />}
|
||||
</button>
|
||||
</form>
|
||||
<p className="text-xs text-slate-400 text-center italic">Demo: Use AURA-2026</p>
|
||||
</motion.div>
|
||||
);
|
||||
|
||||
case 'COLLECT_NICKNAME':
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: 50 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
exit={{ opacity: 0, x: -50 }}
|
||||
className="auth-step-container"
|
||||
>
|
||||
<div className="flex items-center gap-4">
|
||||
<button
|
||||
onClick={() => setStep('COLLECT_EMAIL')}
|
||||
className="p-3 rounded-2xl bg-aura-surface border border-aura-border aura-text-muted hover:aura-text-main transition-all active:scale-90"
|
||||
>
|
||||
<ChevronLeft className="w-5 h-5" />
|
||||
</button>
|
||||
<div className="space-y-1">
|
||||
<span className="text-blue-600 dark:text-blue-400 font-bold text-[10px] uppercase tracking-widest">Step 02</span>
|
||||
<h2 className="text-2xl font-bold aura-text-main">What should we call you?</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<p className="aura-text-muted">This is how Aura will address you.</p>
|
||||
</div>
|
||||
<form onSubmit={handleNext} className="auth-input-container">
|
||||
<User className="auth-input-icon" />
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
placeholder="Your nickname"
|
||||
value={nickname}
|
||||
onChange={(e) => setNickname(e.target.value)}
|
||||
className="auth-input-field"
|
||||
required
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={!nickname || isProcessing}
|
||||
className="auth-submit-btn"
|
||||
>
|
||||
{isProcessing ? <RefreshCw className="w-5 h-5 animate-spin" /> : <ArrowRight className="w-5 h-5" />}
|
||||
</button>
|
||||
</form>
|
||||
</motion.div>
|
||||
);
|
||||
|
||||
case 'COLLECT_FULL_NAME':
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: 50 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
exit={{ opacity: 0, x: -50 }}
|
||||
className="auth-step-container"
|
||||
>
|
||||
<div className="flex items-center gap-4">
|
||||
<button
|
||||
onClick={() => setStep('COLLECT_NICKNAME')}
|
||||
className="p-3 rounded-2xl bg-aura-surface border border-aura-border aura-text-muted hover:aura-text-main transition-all active:scale-90"
|
||||
>
|
||||
<ChevronLeft className="w-5 h-5" />
|
||||
</button>
|
||||
<div className="space-y-1">
|
||||
<span className="text-blue-600 dark:text-blue-400 font-bold text-[10px] uppercase tracking-widest">Step 03</span>
|
||||
<h2 className="text-2xl font-bold aura-text-main">And your full name?</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<p className="aura-text-muted">For your official profile and documents.</p>
|
||||
</div>
|
||||
<form onSubmit={handleNext} className="auth-input-container">
|
||||
<User className="auth-input-icon" />
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
placeholder="First Last"
|
||||
value={fullName}
|
||||
onChange={(e) => setFullName(e.target.value)}
|
||||
className="auth-input-field"
|
||||
required
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={!fullName || isProcessing}
|
||||
className="auth-submit-btn"
|
||||
>
|
||||
{isProcessing ? <RefreshCw className="w-5 h-5 animate-spin" /> : <ArrowRight className="w-5 h-5" />}
|
||||
</button>
|
||||
</form>
|
||||
</motion.div>
|
||||
);
|
||||
|
||||
case 'COLLECT_PHONE':
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: 50 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
exit={{ opacity: 0, x: -50 }}
|
||||
className="auth-step-container"
|
||||
>
|
||||
<div className="flex items-center gap-4">
|
||||
<button
|
||||
onClick={() => setStep('COLLECT_FULL_NAME')}
|
||||
className="p-3 rounded-2xl bg-aura-surface border border-aura-border aura-text-muted hover:aura-text-main transition-all active:scale-90"
|
||||
>
|
||||
<ChevronLeft className="w-5 h-5" />
|
||||
</button>
|
||||
<div className="space-y-1">
|
||||
<span className="text-blue-600 dark:text-blue-400 font-bold text-[10px] uppercase tracking-widest">Step 04</span>
|
||||
<h2 className="text-2xl font-bold aura-text-main">And your phone number?</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<p className="aura-text-muted">We'll use this for secure verification.</p>
|
||||
</div>
|
||||
<form onSubmit={handleNext} className="auth-input-container">
|
||||
<Phone className="auth-input-icon" />
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="tel"
|
||||
placeholder="+1 (555) 000-0000"
|
||||
value={phone}
|
||||
onChange={(e) => setPhone(e.target.value)}
|
||||
className="auth-input-field"
|
||||
required
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={!phone || isProcessing}
|
||||
className="auth-submit-btn"
|
||||
>
|
||||
{isProcessing ? <RefreshCw className="w-5 h-5 animate-spin" /> : <ArrowRight className="w-5 h-5" />}
|
||||
</button>
|
||||
</form>
|
||||
</motion.div>
|
||||
);
|
||||
|
||||
case 'VERIFY_EMAIL':
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: 50 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
exit={{ opacity: 0, x: -50 }}
|
||||
className="space-y-8 w-full max-w-md"
|
||||
>
|
||||
<div className="flex items-center gap-4">
|
||||
<button
|
||||
onClick={() => setStep('COLLECT_PHONE')}
|
||||
className="p-3 rounded-2xl bg-aura-surface border border-aura-border aura-text-muted hover:aura-text-main transition-all active:scale-90"
|
||||
>
|
||||
<ChevronLeft className="w-5 h-5" />
|
||||
</button>
|
||||
<div className="space-y-1">
|
||||
<span className="text-blue-600 dark:text-blue-400 font-bold text-[10px] uppercase tracking-widest">Verification</span>
|
||||
<h2 className="text-2xl font-bold aura-text-main">Check your email</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<p className="aura-text-muted">We sent a 6-digit code to <span className="aura-text-main font-semibold">{email}</span></p>
|
||||
</div>
|
||||
<form onSubmit={handleNext} className="space-y-6">
|
||||
<div className="relative">
|
||||
<ShieldCheck className="absolute left-5 top-1/2 -translate-y-1/2 text-slate-400 w-6 h-6" />
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
maxLength={6}
|
||||
placeholder="000000"
|
||||
value={emailOtp}
|
||||
onChange={(e) => setEmailOtp(e.target.value)}
|
||||
className="aura-input py-5 pl-14 pr-6 text-3xl tracking-[0.5em] font-mono"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<p className="text-xs text-slate-400 text-center italic">Demo: Use 123456</p>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={emailOtp.length < 6 || isProcessing}
|
||||
className="w-full aura-btn-primary py-5 rounded-2xl text-lg shadow-xl disabled:opacity-50"
|
||||
>
|
||||
{isProcessing ? <RefreshCw className="w-5 h-5 animate-spin mx-auto" /> : 'Verify Email'}
|
||||
</button>
|
||||
</form>
|
||||
</motion.div>
|
||||
);
|
||||
|
||||
case 'VERIFY_PHONE':
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: 50 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
exit={{ opacity: 0, x: -50 }}
|
||||
className="space-y-8 w-full max-w-md"
|
||||
>
|
||||
<div className="flex items-center gap-4">
|
||||
<button
|
||||
onClick={() => setStep('VERIFY_EMAIL')}
|
||||
className="p-3 rounded-2xl bg-aura-surface border border-aura-border aura-text-muted hover:aura-text-main transition-all active:scale-90"
|
||||
>
|
||||
<ChevronLeft className="w-5 h-5" />
|
||||
</button>
|
||||
<div className="space-y-1">
|
||||
<span className="text-blue-600 dark:text-blue-400 font-bold text-[10px] uppercase tracking-widest">Verification</span>
|
||||
<h2 className="text-2xl font-bold aura-text-main">One more code</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<p className="aura-text-muted">We sent a text to <span className="aura-text-main font-semibold">{phone}</span></p>
|
||||
</div>
|
||||
<form onSubmit={handleNext} className="space-y-6">
|
||||
<div className="relative">
|
||||
<Smartphone className="absolute left-5 top-1/2 -translate-y-1/2 text-slate-400 w-6 h-6" />
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
maxLength={6}
|
||||
placeholder="000000"
|
||||
value={phoneOtp}
|
||||
onChange={(e) => setPhoneOtp(e.target.value)}
|
||||
className="aura-input py-5 pl-14 pr-6 text-3xl tracking-[0.5em] font-mono"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<p className="text-xs text-slate-400 text-center italic">Demo: Use 123456</p>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={phoneOtp.length < 6 || isProcessing}
|
||||
className="w-full aura-btn-primary py-5 rounded-2xl text-lg shadow-xl disabled:opacity-50"
|
||||
>
|
||||
{isProcessing ? <RefreshCw className="w-5 h-5 animate-spin mx-auto" /> : 'Verify Phone'}
|
||||
</button>
|
||||
</form>
|
||||
</motion.div>
|
||||
);
|
||||
|
||||
case 'SETUP_TOTP':
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.95 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0, scale: 1.05 }}
|
||||
className="space-y-8 w-full max-w-md text-center"
|
||||
>
|
||||
<div className="flex items-center gap-4 text-left">
|
||||
<button
|
||||
onClick={() => setStep('VERIFY_PHONE')}
|
||||
className="p-3 rounded-2xl bg-aura-surface border border-aura-border aura-text-muted hover:aura-text-main transition-all active:scale-90"
|
||||
>
|
||||
<ChevronLeft className="w-5 h-5" />
|
||||
</button>
|
||||
<div className="space-y-1">
|
||||
<span className="text-blue-600 dark:text-blue-400 font-bold text-[10px] uppercase tracking-widest">Security</span>
|
||||
<h2 className="text-2xl font-bold aura-text-main">Setup 2FA</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<p className="aura-text-muted">Scan this QR code with Google Authenticator or Authy.</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-aura-surface p-6 rounded-3xl border-2 border-aura-border inline-block shadow-xl">
|
||||
<QRCodeSVG
|
||||
value={`otpauth://totp/Aura:${email}?secret=JBSWY3DPEHPK3PXP&issuer=Aura`}
|
||||
size={200}
|
||||
level="H"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="aura-row-card text-left">
|
||||
<p className="text-xs aura-text-muted uppercase font-bold mb-1">Manual Secret</p>
|
||||
<code className="text-sm font-mono aura-text-main">JBSWY3DPEHPK3PXP</code>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={() => handleNext()}
|
||||
className="w-full aura-btn-primary py-5 rounded-2xl text-lg shadow-xl"
|
||||
>
|
||||
I've scanned it
|
||||
</button>
|
||||
</motion.div>
|
||||
);
|
||||
|
||||
case 'VERIFY_TOTP':
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: 50 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
exit={{ opacity: 0, x: -50 }}
|
||||
className="space-y-8 w-full max-w-md"
|
||||
>
|
||||
<div className="flex items-center gap-4">
|
||||
<button
|
||||
onClick={() => setStep('SETUP_TOTP')}
|
||||
className="p-3 rounded-2xl bg-aura-surface border border-aura-border aura-text-muted hover:aura-text-main transition-all active:scale-90"
|
||||
>
|
||||
<ChevronLeft className="w-5 h-5" />
|
||||
</button>
|
||||
<div className="space-y-1">
|
||||
<span className="text-blue-600 dark:text-blue-400 font-bold text-[10px] uppercase tracking-widest">Final Step</span>
|
||||
<h2 className="text-2xl font-bold aura-text-main">Verify App Code</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<p className="aura-text-muted">Enter the 6-digit code from your authenticator app.</p>
|
||||
</div>
|
||||
<form onSubmit={handleNext} className="space-y-6">
|
||||
<div className="relative">
|
||||
<KeyRound className="absolute left-5 top-1/2 -translate-y-1/2 text-slate-400 w-6 h-6" />
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
maxLength={6}
|
||||
placeholder="000000"
|
||||
value={totpCode}
|
||||
onChange={(e) => setTotpCode(e.target.value)}
|
||||
className="aura-input py-5 pl-14 pr-6 text-3xl tracking-[0.5em] font-mono"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<p className="text-xs text-slate-400 text-center italic">Demo: Use 123456</p>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={totpCode.length < 6 || isProcessing}
|
||||
className="w-full aura-btn-primary py-5 rounded-2xl text-lg shadow-xl disabled:opacity-50"
|
||||
>
|
||||
{isProcessing ? <RefreshCw className="w-5 h-5 animate-spin mx-auto" /> : 'Complete Setup'}
|
||||
</button>
|
||||
</form>
|
||||
</motion.div>
|
||||
);
|
||||
|
||||
case 'SUCCESS':
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.9 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
className="text-center space-y-8"
|
||||
>
|
||||
<div className="w-24 h-24 bg-emerald-500 rounded-full flex items-center justify-center mx-auto shadow-2xl shadow-emerald-200">
|
||||
<CheckCircle2 className="text-white w-12 h-12" />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<h1 className="text-4xl font-bold aura-text-main tracking-tight">You're all set!</h1>
|
||||
<p className="aura-text-muted text-lg">Welcome to the future of productivity, {nickname || fullName.split(' ')[0]}.</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => handleNext()}
|
||||
className="aura-btn-primary px-12 py-5 rounded-2xl text-xl shadow-2xl"
|
||||
>
|
||||
Enter Aura
|
||||
</button>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={step === 'WELCOME' ? "min-h-screen flex relative overflow-hidden bg-aura-bg" : "auth-container"}>
|
||||
{/* Background Accents */}
|
||||
<div className="fixed top-0 left-0 w-full h-full pointer-events-none overflow-hidden z-0">
|
||||
<div className="absolute top-[-10%] left-[-10%] w-[40%] h-[40%] bg-blue-100 dark:bg-blue-900/20 rounded-full blur-[120px] opacity-50" />
|
||||
<div className="absolute bottom-[-10%] right-[-10%] w-[40%] h-[40%] bg-emerald-100 dark:bg-emerald-900/20 rounded-full blur-[120px] opacity-50" />
|
||||
</div>
|
||||
|
||||
<div className={step === 'WELCOME' ? "w-full h-screen" : "auth-card"}>
|
||||
<AnimatePresence mode="wait">
|
||||
{renderStep()}
|
||||
</AnimatePresence>
|
||||
|
||||
{/* Privacy Policy Overlay */}
|
||||
<AnimatePresence>
|
||||
{showPrivacyPolicy && (
|
||||
<PrivacyPolicy onBack={() => setShowPrivacyPolicy(false)} />
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
|
||||
{/* Progress Indicator */}
|
||||
{step !== 'WELCOME' && step !== 'SUCCESS' && (
|
||||
<div className="auth-progress-indicator">
|
||||
{['COLLECT_INVITE_CODE', 'COLLECT_EMAIL', 'COLLECT_NICKNAME', 'COLLECT_FULL_NAME', 'COLLECT_PHONE', 'VERIFY_EMAIL', 'VERIFY_PHONE', 'SETUP_TOTP', 'VERIFY_TOTP'].map((s, i) => (
|
||||
<div
|
||||
key={s}
|
||||
className={`auth-progress-dot ${
|
||||
step === s ? 'auth-progress-dot-active' : 'auth-progress-dot-inactive'
|
||||
}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
90
src/components/PrivacyPolicy.tsx
Normal file
90
src/components/PrivacyPolicy.tsx
Normal file
@@ -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<PrivacyPolicyProps> = ({ onBack }) => {
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: '100%' }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: '100%' }}
|
||||
transition={{ type: 'spring', damping: 25, stiffness: 200 }}
|
||||
className="fixed inset-0 z-[100] bg-aura-bg overflow-y-auto flex flex-col"
|
||||
>
|
||||
{/* Header with Close Button */}
|
||||
<div className="sticky top-0 z-50 bg-aura-bg/80 backdrop-blur-xl border-b border-aura-border px-6 py-4 flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<Shield className="w-5 h-5 text-blue-600 dark:text-blue-400" />
|
||||
<span className="font-bold uppercase tracking-widest text-xs aura-text-main">Privacy Policy</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={onBack}
|
||||
className="p-2 rounded-full bg-aura-surface border border-aura-border aura-text-muted hover:aura-text-main transition-all active:scale-90"
|
||||
>
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex-grow p-8 md:p-12 max-w-4xl mx-auto w-full">
|
||||
<div className="space-y-12">
|
||||
<div className="flex items-center gap-6">
|
||||
<div className="w-16 h-16 rounded-2xl bg-blue-100 dark:bg-blue-900/30 flex items-center justify-center">
|
||||
<Shield className="w-8 h-8 text-blue-600 dark:text-blue-400" />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-4xl font-bold aura-text-main tracking-tight">Privacy Policy</h1>
|
||||
<p className="aura-text-muted font-medium mt-2">Last Updated: March 23, 2026</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="prose prose-slate dark:prose-invert max-w-none space-y-8">
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-2xl font-bold aura-text-main">1. Introduction</h2>
|
||||
<p className="aura-text-muted leading-relaxed">
|
||||
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.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-2xl font-bold aura-text-main">2. Data Collection</h2>
|
||||
<p className="aura-text-muted leading-relaxed">
|
||||
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.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-2xl font-bold aura-text-main">3. Data Usage</h2>
|
||||
<p className="aura-text-muted leading-relaxed">
|
||||
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.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-2xl font-bold aura-text-main">4. Security</h2>
|
||||
<p className="aura-text-muted leading-relaxed">
|
||||
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.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-2xl font-bold aura-text-main">5. Your Rights</h2>
|
||||
<p className="aura-text-muted leading-relaxed">
|
||||
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.
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div className="pt-12 border-t border-aura-border">
|
||||
<p className="text-sm aura-text-muted">
|
||||
By using Aura Central, you agree to the terms of this Privacy Policy. If you do not agree, please do not use the platform.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
202
src/components/SubscriptionFlow.tsx
Normal file
202
src/components/SubscriptionFlow.tsx
Normal file
@@ -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<SubscriptionFlowProps> = ({ uid, onComplete }) => {
|
||||
const [topUpAmount, setTopUpAmount] = useState<number>(50);
|
||||
const [rechargeLevel, setRechargeLevel] = useState<number>(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 (
|
||||
<div className="min-h-screen bg-aura-bg flex items-center justify-center p-6 overflow-hidden transition-colors duration-300">
|
||||
{/* Background Accents */}
|
||||
<div className="fixed top-0 left-0 w-full h-full pointer-events-none overflow-hidden z-0">
|
||||
<div className="absolute top-[-10%] left-[-10%] w-[40%] h-[40%] bg-blue-100 dark:bg-blue-900/20 rounded-full blur-[120px] opacity-50" />
|
||||
<div className="absolute bottom-[-10%] right-[-10%] w-[40%] h-[40%] bg-indigo-100 dark:bg-indigo-900/20 rounded-full blur-[120px] opacity-50" />
|
||||
</div>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="relative z-10 w-full max-w-4xl grid grid-cols-1 lg:grid-cols-2 gap-8"
|
||||
>
|
||||
{/* Left: Plan Details */}
|
||||
<div className="aura-glass-card p-8 space-y-8">
|
||||
<div className="space-y-2">
|
||||
<div className="aura-logo shadow-blue-200 dark:shadow-blue-900/30">
|
||||
<Zap className="text-white w-6 h-6" />
|
||||
</div>
|
||||
<h1 className="text-3xl font-bold aura-text-main">Choose your Aura</h1>
|
||||
<p className="aura-text-muted">Power your productivity with intelligent credits.</p>
|
||||
</div>
|
||||
|
||||
<div className="aura-gradient-card">
|
||||
<div className="absolute top-0 right-0 p-4 opacity-10">
|
||||
<Zap className="w-24 h-24" />
|
||||
</div>
|
||||
<div className="relative z-10 space-y-4">
|
||||
<div className="flex justify-between items-start">
|
||||
<div>
|
||||
<h3 className="text-xl font-bold">Aura Central Platform</h3>
|
||||
<p className="text-blue-100 text-sm">Monthly Access Fee</p>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<span className="text-3xl font-bold">$29</span>
|
||||
<span className="text-blue-200 text-sm">/mo</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
{[
|
||||
'Unified Communication Hub',
|
||||
'Omnichannel AI Assistant',
|
||||
'WhatsApp, Web, & Voice',
|
||||
'Priority Security & Privacy'
|
||||
].map((feature, i) => (
|
||||
<div key={i} className="flex items-center gap-2 text-sm">
|
||||
<Check className="w-4 h-4 text-blue-300" />
|
||||
<span>{feature}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="aura-row-card border aura-border">
|
||||
<ShieldCheck className="text-emerald-500 w-6 h-6" />
|
||||
<div className="text-xs aura-text-muted">
|
||||
<p className="font-bold aura-text-main">Secure Billing</p>
|
||||
<p>Encrypted by industry-standard protocols.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right: Usage Configuration */}
|
||||
<div className="aura-glass-card p-8 flex flex-col justify-between border-2 border-aura-accent-blue/20">
|
||||
<div className="space-y-8">
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 bg-amber-100 dark:bg-amber-900/30 rounded-lg flex items-center justify-center">
|
||||
<Coins className="text-amber-600 dark:text-amber-400 w-5 h-5" />
|
||||
</div>
|
||||
<h2 className="text-xl font-bold aura-text-main">Usage Configuration</h2>
|
||||
</div>
|
||||
<p className="aura-text-muted text-xs leading-relaxed">Set up your pay-as-you-go usage credits. You only pay for what you consume.</p>
|
||||
|
||||
<div className="space-y-4">
|
||||
<label className="section-title">Select Amount</label>
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
{[25, 50, 100].map((amount) => (
|
||||
<button
|
||||
key={amount}
|
||||
onClick={() => setTopUpAmount(amount)}
|
||||
className={`py-3 rounded-xl font-bold transition-all border-2 ${
|
||||
topUpAmount === amount
|
||||
? 'bg-aura-text text-aura-surface border-transparent shadow-lg'
|
||||
: 'bg-aura-surface text-aura-text-muted aura-border hover:border-blue-500/30'
|
||||
}`}
|
||||
>
|
||||
${amount}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 bg-indigo-100 dark:bg-indigo-900/30 rounded-lg flex items-center justify-center">
|
||||
<TrendingUp className="text-indigo-600 dark:text-indigo-400 w-5 h-5" />
|
||||
</div>
|
||||
<h2 className="text-xl font-bold aura-text-main">Auto-Recharge</h2>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="flex justify-between items-end">
|
||||
<label className="section-title">Recharge Level</label>
|
||||
<span className="text-aura-accent-indigo font-bold text-lg">${rechargeLevel}</span>
|
||||
</div>
|
||||
<input
|
||||
type="range"
|
||||
min="5"
|
||||
max="50"
|
||||
step="5"
|
||||
value={rechargeLevel}
|
||||
onChange={(e) => 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"
|
||||
/>
|
||||
<p className="text-xs aura-text-muted italic">
|
||||
We'll automatically top up your account with ${topUpAmount} when your balance falls below ${rechargeLevel}.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-12 space-y-4">
|
||||
<div className="flex justify-between items-center aura-text-main">
|
||||
<span className="font-medium">Total Due Today</span>
|
||||
<span className="text-2xl font-bold">${29 + topUpAmount}</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleComplete}
|
||||
disabled={isProcessing}
|
||||
className="w-full aura-btn-primary py-5 rounded-2xl text-lg shadow-xl disabled:opacity-50"
|
||||
>
|
||||
{isProcessing ? (
|
||||
<>
|
||||
<RefreshCw className="w-5 h-5 animate-spin" />
|
||||
Processing...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
Confirm Subscription
|
||||
<ArrowRight className="w-5 h-5" />
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
88
src/firebase.ts
Normal file
88
src/firebase.ts
Normal file
@@ -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 };
|
||||
29
src/index.css
Normal file
29
src/index.css
Normal file
@@ -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";
|
||||
10
src/main.tsx
Normal file
10
src/main.tsx
Normal file
@@ -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(
|
||||
<StrictMode>
|
||||
<App />
|
||||
</StrictMode>,
|
||||
);
|
||||
67
src/styles/aura.css
Normal file
67
src/styles/aura.css
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
45
src/styles/auth.css
Normal file
45
src/styles/auth.css
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
49
src/styles/base.css
Normal file
49
src/styles/base.css
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
108
src/styles/components.css
Normal file
108
src/styles/components.css
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
47
src/styles/layout.css
Normal file
47
src/styles/layout.css
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
62
src/styles/theme.css
Normal file
62
src/styles/theme.css
Normal file
@@ -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;
|
||||
}
|
||||
28
src/styles/utilities.css
Normal file
28
src/styles/utilities.css
Normal file
@@ -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;
|
||||
}
|
||||
@@ -10,9 +10,6 @@
|
||||
"DOM.Iterable"
|
||||
],
|
||||
"skipLibCheck": true,
|
||||
"types": [
|
||||
"node"
|
||||
],
|
||||
"moduleResolution": "bundler",
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force",
|
||||
|
||||
@@ -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';
|
||||
|
||||
// FIX: Derive __dirname in ESM environment to resolve "Cannot find name '__dirname'" error
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
import path from 'path';
|
||||
import {defineConfig, loadEnv} from 'vite';
|
||||
|
||||
export default defineConfig(({mode}) => {
|
||||
const env = loadEnv(mode, '.', '');
|
||||
return {
|
||||
server: {
|
||||
port: 3000,
|
||||
host: '0.0.0.0',
|
||||
},
|
||||
plugins: [react()],
|
||||
plugins: [react(), tailwindcss()],
|
||||
define: {
|
||||
'process.env.API_KEY': JSON.stringify(env.GEMINI_API_KEY),
|
||||
'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY)
|
||||
'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY),
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, '.'),
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
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',
|
||||
},
|
||||
};
|
||||
});
|
||||
Reference in New Issue
Block a user