diff --git a/README.md b/README.md index 162846e8..ed98ff26 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,9 @@

- - Live Demo - + + CI Build Status + Contributors Forks Stargazers diff --git a/next.config.ts b/next.config.ts index 2c23b68a..85f78eb8 100644 --- a/next.config.ts +++ b/next.config.ts @@ -2,7 +2,6 @@ import type { NextConfig } from 'next'; const nextConfig: NextConfig = { /* config options here */ - output: 'export', devIndicators: { // @ts-ignore - buildActivity is valid but missing in type definition buildActivity: false, diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index 0f8810f8..63ec86e3 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -28,7 +28,7 @@ import AdminKeyModal from '@/components/auth/AdminKeyModal'; import { useAuth } from '@/context/AuthContext'; import { useNotificationActions } from '@/stores/ui-store'; import { useMaintenance } from '@/hooks/useMaintenance'; -import { auth, db } from '@/lib/firebase'; +import { auth, db, isFirebaseConfigured } from '@/lib/firebase'; export default function LoginPage() { const [email, setEmail] = useState(''); @@ -113,6 +113,15 @@ export default function LoginPage() { setIsCheckingAdmin(true); try { + if (!isFirebaseConfigured) { + const message = + 'Login is temporarily unavailable because Firebase is not configured.'; + setError(message); + showError(message); + setIsCheckingAdmin(false); + return; + } + await login(normalizedEmail, password); const adminDoc = await getDoc(doc(db, 'admins', normalizedEmail)); @@ -168,6 +177,15 @@ export default function LoginPage() { setIsCheckingAdmin(true); try { + if (!isFirebaseConfigured) { + const message = + 'Sign-in is temporarily unavailable because Firebase is not configured.'; + setError(message); + showError(message); + setIsCheckingAdmin(false); + return; + } + const provider = providerName === 'google' ? new GoogleAuthProvider() diff --git a/src/app/profile/[username]/page.tsx b/src/app/profile/[username]/page.tsx index ec4b3ee9..689cd30f 100644 --- a/src/app/profile/[username]/page.tsx +++ b/src/app/profile/[username]/page.tsx @@ -3,7 +3,9 @@ import { Metadata } from 'next'; import { notFound } from 'next/navigation'; +import { collection, getDocs, query, where } from 'firebase/firestore'; import { getPublicProfileByUsername } from '@/lib/portfolio-service'; +import { db, isFirebaseConfigured } from '@/lib/firebase'; import { ProfileHeader } from '@/components/profile/ProfileHeader'; import { PathProgressSection } from '@/components/profile/PathProgressSection'; import { SkillBadgesSection } from '@/components/profile/SkillBadgesSection'; @@ -14,9 +16,35 @@ interface Props { params: { username: string }; } +export const dynamicParams = false; + +export async function generateStaticParams() { + if (!isFirebaseConfigured) return []; + + try { + const profilesQuery = query( + collection(db, 'portfolios'), + where('isPublic', '==', true) + ); + const snapshot = await getDocs(profilesQuery); + + return snapshot.docs + .map((profileDoc) => profileDoc.data().username) + .filter((username): username is string => Boolean(username)) + .map((username) => ({ username })); + } catch { + return []; + } +} + // Generate OpenGraph metadata dynamically -export async function generateMetadata({ params }: Props): Promise { - const profile = await getPublicProfileByUsername(params.username); +export async function generateMetadata({ + params, +}: { + params: Promise<{ username: string }>; +}): Promise { + const { username } = await params; + const profile = await getPublicProfileByUsername(username); if (!profile) return { title: 'Profile not found' }; return { @@ -30,8 +58,13 @@ export async function generateMetadata({ params }: Props): Promise { }; } -export default async function PublicProfilePage({ params }: Props) { - const profile = await getPublicProfileByUsername(params.username); +export default async function PublicProfilePage({ + params, +}: { + params: Promise<{ username: string }>; +}) { + const { username } = await params; + const profile = await getPublicProfileByUsername(username); if (!profile) notFound(); diff --git a/src/lib/firebase.ts b/src/lib/firebase.ts index 146c99c9..6801dc78 100644 --- a/src/lib/firebase.ts +++ b/src/lib/firebase.ts @@ -3,67 +3,53 @@ import { getAnalytics, isSupported } from 'firebase/analytics'; import { getFirestore, Firestore } from 'firebase/firestore'; import { getAuth, Auth } from 'firebase/auth'; -const isProduction = process.env.NODE_ENV === 'production'; - const firebaseConfig = { apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY, authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN, projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID, storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET, messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID, + appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID, + measurementId: process.env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID, }; -// Production Safety Guard -if (isProduction) { - const missingKeys = Object.entries(firebaseConfig) - .filter(([_, value]) => !value) - .map(([key]) => key); - - if (missingKeys.length > 0) { - throw new Error( - `Production Deployment Error: Missing required Firebase environment variables: ${missingKeys.join(', ')}` - ); - } -} else { - // Safe Mock Fallbacks strictly for local development/testing only - firebaseConfig.apiKey = - firebaseConfig.apiKey || 'mock-api-key-for-local-testing-only-123456'; - firebaseConfig.authDomain = - firebaseConfig.authDomain || 'mock-app.firebaseapp.com'; - firebaseConfig.projectId = firebaseConfig.projectId || 'mock-app-id'; - firebaseConfig.storageBucket = - firebaseConfig.storageBucket || 'mock-app.appspot.com'; - firebaseConfig.messagingSenderId = - firebaseConfig.messagingSenderId || '1234567890'; -} - -const isFirebaseConfigValid = Boolean( +const isFirebaseConfigured = Boolean( firebaseConfig.apiKey && firebaseConfig.authDomain && firebaseConfig.projectId && firebaseConfig.storageBucket && - firebaseConfig.messagingSenderId + firebaseConfig.messagingSenderId && + firebaseConfig.appId ); -const app: FirebaseApp | null = isFirebaseConfigValid +const app: FirebaseApp | null = isFirebaseConfigured ? !getApps().length ? initializeApp(firebaseConfig) : getApp() : null; -if (!app) { - console.warn( - 'Firebase is not configured. Running in local UI-only mode without Firebase auth or Firestore.' - ); -} - -// For build-time typing we export `db` as a Firestore instance. At runtime -// `app` may be null when Firebase isn't configured; in that case we provide -// a minimal cast to satisfy TypeScript while keeping runtime behavior safe. const db: Firestore = app ? (getFirestore(app) as Firestore) : (null as unknown as Firestore); const auth: Auth = app ? (getAuth(app) as Auth) : ({} as Auth); const firebaseAvailable = Boolean(app); -export { db, auth, firebaseAvailable, firebaseConfig }; +let analytics; + +if (app && typeof window !== 'undefined') { + isSupported().then((supported) => { + if (supported) { + analytics = getAnalytics(app); + } + }); +} + +export { + app, + analytics, + db, + auth, + firebaseAvailable, + firebaseConfig, + isFirebaseConfigured, +};