Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions FIREBASE_SETUP.md
Original file line number Diff line number Diff line change
@@ -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
187 changes: 119 additions & 68 deletions app/dashboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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) ? (
<span key={index} className="bg-yellow-200 dark:bg-yellow-800 px-1 rounded">
{part}
</span>
) : 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 (
<div className="min-h-screen bg-gradient-to-br from-sky-50 to-blue-100 dark:from-gray-900 dark:to-blue-900 flex items-center justify-center">
<div className="animate-spin rounded-full h-32 w-32 border-b-2 border-blue-600"></div>
</div>
);
}

return (
<div className="min-h-screen bg-gradient-to-br from-sky-50 to-blue-100 dark:from-gray-900 dark:to-blue-900">
Expand All @@ -64,6 +82,18 @@ export default function Dashboard() {
FlightBrief AI
</h1>
</div>
<div className="flex items-center space-x-4">
<span className="text-sm text-gray-600 dark:text-gray-400">
{user?.displayName || user?.email}
</span>
<button
onClick={handleLogout}
className="inline-flex items-center px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 transition-colors duration-200"
>
<FontAwesomeIcon icon={faSignOutAlt} className="mr-2" />
Logout
</button>
</div>
</div>
</div>
</div>
Expand All @@ -74,10 +104,10 @@ export default function Dashboard() {
{/* Welcome Message */}
<div className="mb-6">
<h2 className="text-3xl font-bold text-gray-900 dark:text-white">
Welcome back, <span className="text-blue-600 dark:text-blue-400">Cody</span>
Welcome back, <span className="text-blue-600 dark:text-blue-400">{user?.displayName || "User"}</span>
</h2>
<p className="text-gray-600 dark:text-gray-400 mt-2">
Here are your recent flight reports and analytics
Manage your aviation reports and analytics
</p>
</div>

Expand Down Expand Up @@ -118,35 +148,56 @@ export default function Dashboard() {
{/* Reports List */}
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700">
<div className="px-6 py-4 border-b border-gray-200 dark:border-gray-700">
<h3 className="text-lg font-medium text-gray-900 dark:text-white">
Recent Reports
</h3>
<div className="flex items-center justify-between">
<h3 className="text-lg font-medium text-gray-900 dark:text-white">
Recent Reports
</h3>
{searchQuery.trim() && (
<span className="text-sm text-gray-500 dark:text-gray-400">
{filteredReports.length} of {exampleReports.length} reports
</span>
)}
</div>
</div>

<div className="divide-y divide-gray-200 dark:divide-gray-700">
{exampleReports.map((report) => (
<div key={report.id} className="px-6 py-4 hover:bg-gray-50 dark:hover:bg-gray-700 cursor-pointer transition-colors duration-150">
<div className="flex items-center justify-between">
<div className="flex-1">
<h4 className="text-sm font-medium text-gray-900 dark:text-white">
{report.name}
</h4>
<p className="text-sm text-gray-500 dark:text-gray-400 mt-1">
{report.type}
</p>
</div>
<div className="text-right">
<p className="text-sm text-gray-900 dark:text-white">
{new Date(report.date).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric'
})}
</p>
</div>
</div>
{filteredReports.length === 0 ? (
<div className="px-6 py-8 text-center">
<FontAwesomeIcon icon={faSearch} className="h-12 w-12 text-gray-400 mx-auto mb-4" />
<p className="text-gray-500 dark:text-gray-400">
{searchQuery.trim() ? `No reports found for "${searchQuery}"` : "No reports available"}
</p>
<p className="text-sm text-gray-400 dark:text-gray-500 mt-1">
Create your first report to get started
</p>
</div>
))}
) : (
filteredReports.map((report) => (
<Link key={report.id} href={`/report/${report.id}`}>
<div className="px-6 py-4 hover:bg-gray-50 dark:hover:bg-gray-700 cursor-pointer transition-colors duration-150">
<div className="flex items-center justify-between">
<div className="flex-1">
<h4 className="text-sm font-medium text-gray-900 dark:text-white">
{highlightText(report.name, searchQuery)}
</h4>
<p className="text-sm text-gray-500 dark:text-gray-400 mt-1">
{highlightText(report.type, searchQuery)}
</p>
</div>
<div className="text-right">
<p className="text-sm text-gray-900 dark:text-white">
{new Date(report.date).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric'
})}
</p>
</div>
</div>
</div>
</Link>
))
)}
</div>
</div>
</div>
Expand Down
5 changes: 4 additions & 1 deletion app/layout.tsx
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -27,7 +28,9 @@ export default function RootLayout({
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
{children}
<AuthProvider>
{children}
</AuthProvider>
</body>
</html>
);
Expand Down
Loading