diff --git a/FIREBASE_SETUP.md b/FIREBASE_SETUP.md new file mode 100644 index 0000000..18939de --- /dev/null +++ b/FIREBASE_SETUP.md @@ -0,0 +1,65 @@ +# Firebase Authentication Setup + +This application uses Firebase Authentication for user management. Follow these steps to configure Firebase: + +## 1. Create a Firebase Project + +1. Go to [Firebase Console](https://console.firebase.google.com/) +2. Click "Create a project" or "Add project" +3. Enter your project name (e.g., "aviation-sales-report") +4. Follow the setup wizard + +## 2. Enable Authentication + +1. In your Firebase project, go to "Authentication" in the left sidebar +2. Click "Get started" +3. Go to the "Sign-in method" tab +4. Enable "Email/Password" authentication method + +## 3. Get Your Firebase Configuration + +1. Go to Project Settings (gear icon in the left sidebar) +2. Scroll down to "Your apps" section +3. Click "Add app" and select the web icon () +4. Register your app with a nickname +5. Copy the Firebase configuration object + +## 4. Configure Environment Variables + +Create a `.env.local` file in the root directory and add your Firebase configuration: + +```env +NEXT_PUBLIC_FIREBASE_API_KEY=your-api-key +NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=your-project.firebaseapp.com +NEXT_PUBLIC_FIREBASE_PROJECT_ID=your-project-id +NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=your-project.appspot.com +NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=123456789 +NEXT_PUBLIC_FIREBASE_APP_ID=your-app-id +``` + +Replace the values with your actual Firebase configuration values. + +## 5. Test the Authentication + +1. Run the development server: `npm run dev` +2. Navigate to `/login` +3. Try creating a new account with the "Sign Up" tab +4. Try signing in with existing credentials +5. Test the logout functionality from the dashboard + +## Security Notes + +- Never commit your `.env.local` file to version control +- The `.env.local` file is already included in `.gitignore` +- All Firebase configuration values starting with `NEXT_PUBLIC_` are safe to expose in the browser +- Make sure to configure Firebase security rules for production use + +## Features Included + +- ✅ Email/Password authentication +- ✅ User registration (Sign Up) +- ✅ User login (Sign In) +- ✅ User logout +- ✅ Protected routes (dashboard redirects to login if not authenticated) +- ✅ Loading states and error handling +- ✅ Responsive design with dark mode support diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx index b4263af..e95ebc0 100644 --- a/app/dashboard/page.tsx +++ b/app/dashboard/page.tsx @@ -2,52 +2,70 @@ import Link from "next/link"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faPlus, faSearch } from "@fortawesome/free-solid-svg-icons"; +import { faPlus, faSearch, faSignOutAlt } from "@fortawesome/free-solid-svg-icons"; import FlightBriefLogo from "@/components/FlightBriefLogo"; -import { useState } from "react"; +import { useState, useEffect } from "react"; +import { useAuth } from "@/contexts/AuthContext"; +import { useRouter } from "next/navigation"; -// Example reports data -const exampleReports = [ - { - id: 1, - name: "Q3 Flight Operations Summary", - date: "2025-09-10", - type: "Operations Report" - }, - { - id: 2, - name: "Aircraft Maintenance Analysis", - date: "2025-09-08", - type: "Maintenance Report" - }, - { - id: 3, - name: "Safety Incident Review - August", - date: "2025-09-01", - type: "Safety Report" - }, - { - id: 4, - name: "Fleet Utilization Metrics", - date: "2025-08-28", - type: "Performance Report" - }, - { - id: 5, - name: "Fuel Cost Analysis - Summer 2025", - date: "2025-08-25", - type: "Financial Report" - }, - { - id: 6, - name: "Pilot Training Progress Update", - date: "2025-08-20", - type: "Training Report" - } -]; +// Reports data - will be fetched from database +const exampleReports: any[] = []; export default function Dashboard() { const [searchQuery, setSearchQuery] = useState(""); + const { user, logout } = useAuth(); + const router = useRouter(); + + + // Filter reports based on search query + const filteredReports = exampleReports.filter(report => { + if (!searchQuery.trim()) return true; + + const query = searchQuery.toLowerCase(); + return ( + report.name.toLowerCase().includes(query) || + report.type.toLowerCase().includes(query) + ); + }); + + // Function to highlight search terms + const highlightText = (text: string, query: string) => { + if (!query.trim()) return text; + + const regex = new RegExp(`(${query})`, 'gi'); + const parts = text.split(regex); + + return parts.map((part, index) => + regex.test(part) ? ( + + {part} + + ) : part + ); + }; + + useEffect(() => { + if (!user) { + router.push("/login"); + } + }, [user, router]); + + const handleLogout = async () => { + try { + await logout(); + router.push("/login"); + } catch (error) { + console.error("Failed to log out", error); + } + }; + + if (!user) { + return ( +
+
+
+ ); + } return (
@@ -64,6 +82,18 @@ export default function Dashboard() { FlightBrief AI
+
+ + {user?.displayName || user?.email} + + +
@@ -74,10 +104,10 @@ export default function Dashboard() { {/* Welcome Message */}

- Welcome back, Cody + Welcome back, {user?.displayName || "User"}

- Here are your recent flight reports and analytics + Manage your aviation reports and analytics

@@ -118,35 +148,56 @@ export default function Dashboard() { {/* Reports List */}
-

- Recent Reports -

+
+

+ Recent Reports +

+ {searchQuery.trim() && ( + + {filteredReports.length} of {exampleReports.length} reports + + )} +
- {exampleReports.map((report) => ( -
-
-
-

- {report.name} -

-

- {report.type} -

-
-
-

- {new Date(report.date).toLocaleDateString('en-US', { - month: 'short', - day: 'numeric', - year: 'numeric' - })} -

-
-
+ {filteredReports.length === 0 ? ( +
+ +

+ {searchQuery.trim() ? `No reports found for "${searchQuery}"` : "No reports available"} +

+

+ Create your first report to get started +

- ))} + ) : ( + filteredReports.map((report) => ( + +
+
+
+

+ {highlightText(report.name, searchQuery)} +

+

+ {highlightText(report.type, searchQuery)} +

+
+
+

+ {new Date(report.date).toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + year: 'numeric' + })} +

+
+
+
+ + )) + )}
diff --git a/app/layout.tsx b/app/layout.tsx index f7fa87e..29ecbca 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,6 +1,7 @@ import type { Metadata } from "next"; import { Geist, Geist_Mono } from "next/font/google"; import "./globals.css"; +import { AuthProvider } from "@/contexts/AuthContext"; const geistSans = Geist({ variable: "--font-geist-sans", @@ -27,7 +28,9 @@ export default function RootLayout({ - {children} + + {children} + ); diff --git a/app/login/page.tsx b/app/login/page.tsx index 6df5fd9..ab7e5de 100644 --- a/app/login/page.tsx +++ b/app/login/page.tsx @@ -4,16 +4,48 @@ import Link from "next/link"; import { useRouter } from "next/navigation"; import FlightBriefLogo from "@/components/FlightBriefLogo"; import { useState } from "react"; +import { useAuth } from "@/contexts/AuthContext"; export default function Login() { const router = useRouter(); + const { login, signup } = useAuth(); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); + const [confirmPassword, setConfirmPassword] = useState(""); + const [username, setUsername] = useState(""); + const [isSignUp, setIsSignUp] = useState(false); + const [error, setError] = useState(""); + const [loading, setLoading] = useState(false); - const handleSignIn = (e: React.FormEvent) => { + const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); - // Navigate to dashboard (no backend validation for now) - router.push("/dashboard"); + setError(""); + setLoading(true); + + try { + if (isSignUp) { + if (!username.trim()) { + setError("Username is required"); + return; + } + if (password !== confirmPassword) { + setError("Passwords don't match"); + return; + } + if (password.length < 6) { + setError("Password should be at least 6 characters"); + return; + } + await signup(email, password, username); + } else { + await login(email, password); + } + router.push("/dashboard"); + } catch (error: any) { + setError(error.message || "An error occurred"); + } finally { + setLoading(false); + } }; return ( @@ -32,12 +64,63 @@ export default function Login() { FlightBrief AI

- Sign in to your account + {isSignUp ? "Create your account" : "Sign in to your account"}

- {/* Login Form */} -
+ {/* Toggle between Sign In and Sign Up */} +
+ + +
+ + {/* Error Message */} + {error && ( +
+ {error} +
+ )} + + {/* Auth Form */} + + {/* Username field for Sign Up */} + {isSignUp && ( +
+ + setUsername(e.target.value)} + className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white" + placeholder="Enter your username" + /> +
+ )} +
+ {/* Confirm Password field for Sign Up */} + {isSignUp && ( +
+ + setConfirmPassword(e.target.value)} + className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white" + placeholder="Confirm your password" + minLength={6} + /> +
+ )} +
diff --git a/app/report/[id]/page.tsx b/app/report/[id]/page.tsx index 38e1251..ddc9457 100644 --- a/app/report/[id]/page.tsx +++ b/app/report/[id]/page.tsx @@ -4,33 +4,176 @@ import Link from "next/link"; import FlightBriefLogo from "@/components/FlightBriefLogo"; import { useState, useEffect } from "react"; import { useParams } from "next/navigation"; +import { useAuth } from "@/contexts/AuthContext"; +import { useRouter } from "next/navigation"; +import jsPDF from "jspdf"; + +// Report data - will be fetched from database +const sampleReports: any[] = []; export default function ReportWithId() { const params = useParams(); - const reportId = params?.id as string; + const { user } = useAuth(); + const router = useRouter(); + const reportId = parseInt(params?.id as string); - const [aircraftId, setAircraftId] = useState(""); - const [reportData, setReportData] = useState(""); - const [isProcessing, setIsProcessing] = useState(false); + const [report, setReport] = useState(null); + const [isDownloading, setIsDownloading] = useState(false); + const [shareMenuOpen, setShareMenuOpen] = useState(false); useEffect(() => { - if (reportId) { - // Load existing report data based on ID - setReportData(`Loading existing report with ID: ${reportId}\n\nReport data would be loaded here...`); + if (!user) { + router.push("/login"); + return; + } + + // Find the report by ID + const foundReport = sampleReports.find(r => r.id === reportId); + if (foundReport) { + setReport(foundReport); } - }, [reportId]); + }, [reportId, user, router]); + + // Close share menu when clicking outside + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (shareMenuOpen) { + const target = event.target as HTMLElement; + if (!target.closest('.share-menu-container')) { + setShareMenuOpen(false); + } + } + }; - const handleProcess = () => { - if (!aircraftId.trim()) return; + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, [shareMenuOpen]); + + const handleDownloadPDF = async () => { + if (!report) return; - setIsProcessing(true); - // Simulate processing - in the future this will call your LLM - setTimeout(() => { - setReportData(`Processing aircraft: ${aircraftId}\n\nReport data will appear here...`); - setIsProcessing(false); - }, 2000); + setIsDownloading(true); + try { + const pdf = new jsPDF(); + const pageWidth = pdf.internal.pageSize.getWidth(); + const pageHeight = pdf.internal.pageSize.getHeight(); + const margin = 20; + const maxWidth = pageWidth - 2 * margin; + + // Add title + pdf.setFontSize(16); + pdf.setFont('helvetica', 'bold'); + pdf.text(report.name, margin, 30); + + // Add metadata + pdf.setFontSize(10); + pdf.setFont('helvetica', 'normal'); + pdf.text(`Report ID: ${report.id}`, margin, 45); + pdf.text(`Generated: ${report.date}`, margin, 55); + pdf.text(`Type: ${report.type}`, margin, 65); + + // Add content + pdf.setFontSize(9); + const lines = pdf.splitTextToSize(report.content, maxWidth); + let yPosition = 80; + + for (let i = 0; i < lines.length; i++) { + if (yPosition > pageHeight - margin) { + pdf.addPage(); + yPosition = margin; + } + pdf.text(lines[i], margin, yPosition); + yPosition += 5; + } + + // Save the PDF + pdf.save(`${report.name.replace(/[^a-z0-9]/gi, '_').toLowerCase()}.pdf`); + } catch (error) { + console.error('Error generating PDF:', error); + alert('Error generating PDF. Please try again.'); + } finally { + setIsDownloading(false); + } + }; + + const handleShareReport = () => { + setShareMenuOpen(!shareMenuOpen); + }; + + const copyReportLink = async () => { + const currentUrl = window.location.href; + try { + await navigator.clipboard.writeText(currentUrl); + alert('Report link copied to clipboard!'); + setShareMenuOpen(false); + } catch (error) { + console.error('Error copying to clipboard:', error); + alert('Error copying link. Please try again.'); + } }; + const shareViaEmail = () => { + const subject = encodeURIComponent(`Aviation Report: ${report?.name}`); + const body = encodeURIComponent( + `Hi,\n\nI wanted to share this aviation report with you:\n\n${report?.name}\nGenerated: ${report?.date}\nType: ${report?.type}\n\nView the full report here: ${window.location.href}\n\nBest regards` + ); + window.open(`mailto:?subject=${subject}&body=${body}`); + setShareMenuOpen(false); + }; + + if (!user) { + return ( +
+
+
+

Loading...

+
+
+ ); + } + + if (!report) { + return ( +
+
+
+
+
+ +

+ FlightBrief AI +

+
+ + ← Back to Dashboard + +
+
+
+
+
+

No Report Available

+

No report data found. Please create a new report.

+ + Return to Dashboard + +
+
+
+ ); + } + return (
{/* Header */} @@ -58,58 +201,85 @@ export default function ReportWithId() { {/* Main Content */}
- {/* Page Title */} + {/* Report Header */}
-

- {reportId ? `Edit Report ${reportId}` : "Create Flight Report"} -

-

- {reportId - ? "Viewing and editing existing flight report" - : "Enter aircraft information to generate a comprehensive flight report" - } -

+
+

+ {report.name} +

+ + {report.type} + +
+
+ Report ID: {report.id} + + Generated: {report.date} +
- {/* Input Section */} -
-
- -
- setAircraftId(e.target.value)} - placeholder="e.g., N123AB or A1B2C3" - className="flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white" - /> -
+ + {/* Action Buttons */} +
+ + ← Back to Dashboard + +
+ +
+ + + {/* Share Menu */} + {shareMenuOpen && ( +
+
+ + +
+
+ )}
- - {/* Report Output Section */} -
-

- Report Output -

-