diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..7c6a1bc
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,9 @@
+# Vite only exposes env vars prefixed with VITE_
+# Firebase Web config (these values are not private keys, but still treat your .env as local-only)
+VITE_FIREBASE_API_KEY=
+VITE_FIREBASE_AUTH_DOMAIN=
+VITE_FIREBASE_PROJECT_ID=
+VITE_FIREBASE_STORAGE_BUCKET=
+VITE_FIREBASE_MESSAGING_SENDER_ID=
+VITE_FIREBASE_APP_ID=
+VITE_FIREBASE_MEASUREMENT_ID=
diff --git a/.gitignore b/.gitignore
index 8a68186..1b8cf72 100644
--- a/.gitignore
+++ b/.gitignore
@@ -28,5 +28,4 @@ pnpm-lock.yaml
pnpm-workspace.yaml
# Environment variables
-.env
-.env*
\ No newline at end of file
+.env
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
index 749b3c4..3c13128 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -2,6 +2,14 @@ FROM node:22 AS builder
WORKDIR /app
+ARG VITE_FIREBASE_API_KEY
+ARG VITE_FIREBASE_AUTH_DOMAIN
+ARG VITE_FIREBASE_PROJECT_ID
+ARG VITE_FIREBASE_STORAGE_BUCKET
+ARG VITE_FIREBASE_MESSAGING_SENDER_ID
+ARG VITE_FIREBASE_APP_ID
+ARG VITE_FIREBASE_MEASUREMENT_ID
+
# Copy package*.json
COPY package*.json .
@@ -12,6 +20,13 @@ RUN npm install
COPY . .
# Build the application
+ENV VITE_FIREBASE_API_KEY=$VITE_FIREBASE_API_KEY \
+ VITE_FIREBASE_AUTH_DOMAIN=$VITE_FIREBASE_AUTH_DOMAIN \
+ VITE_FIREBASE_PROJECT_ID=$VITE_FIREBASE_PROJECT_ID \
+ VITE_FIREBASE_STORAGE_BUCKET=$VITE_FIREBASE_STORAGE_BUCKET \
+ VITE_FIREBASE_MESSAGING_SENDER_ID=$VITE_FIREBASE_MESSAGING_SENDER_ID \
+ VITE_FIREBASE_APP_ID=$VITE_FIREBASE_APP_ID \
+ VITE_FIREBASE_MEASUREMENT_ID=$VITE_FIREBASE_MEASUREMENT_ID
RUN npm run build
FROM nginx:alpine
diff --git a/docker-compose.yml b/docker-compose.yml
index 2879854..d99c84a 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,18 +1,19 @@
services:
app:
- build: .
- environment:
- apiKey: ${apiKey}
- authDomain: ${authDomain}
- projectId: ${projectId}
- storageBucket: ${storageBucket}
- messagingSenderId: ${messagingSenderId}
- appId: ${appId}
- measurementId: ${measurementId}
+ build:
+ context: .
+ args:
+ VITE_FIREBASE_API_KEY: ${VITE_FIREBASE_API_KEY}
+ VITE_FIREBASE_AUTH_DOMAIN: ${VITE_FIREBASE_AUTH_DOMAIN}
+ VITE_FIREBASE_PROJECT_ID: ${VITE_FIREBASE_PROJECT_ID}
+ VITE_FIREBASE_STORAGE_BUCKET: ${VITE_FIREBASE_STORAGE_BUCKET}
+ VITE_FIREBASE_MESSAGING_SENDER_ID: ${VITE_FIREBASE_MESSAGING_SENDER_ID}
+ VITE_FIREBASE_APP_ID: ${VITE_FIREBASE_APP_ID}
+ VITE_FIREBASE_MEASUREMENT_ID: ${VITE_FIREBASE_MEASUREMENT_ID}
ports:
- "80"
deploy:
- replicas: 1
+ replicas: 3
restart_policy:
condition: on-failure
rollback_config:
diff --git a/firebase.ts b/firebase.ts
index 6e24b50..65f44b4 100644
--- a/firebase.ts
+++ b/firebase.ts
@@ -1,22 +1 @@
-// Import the functions you need from the SDKs you need
-import { initializeApp } from "firebase/app";
-import { getFirestore } from "firebase/firestore";
-// TODO: Add SDKs for Firebase products that you want to use
-// https://firebase.google.com/docs/web/setup#available-libraries
-
-// Your web app's Firebase configuration
-// For Firebase JS SDK v7.20.0 and later, measurementId is optional
-const firebaseConfig = {
- apiKey: process.env.apiKey,
- authDomain: process.env.authDomain,
- projectId: process.env.projectId,
- storageBucket: process.env.storageBucket,
- messagingSenderId: process.env.messagingSenderId,
- appId: process.env.appId,
- measurementId: process.env.measurementId
-};
-
-// Initialize Firebase
-const app = initializeApp(firebaseConfig);
-const db = getFirestore(app);
-export { app, db };
\ No newline at end of file
+export { app, db } from "./src/firebase.ts";
\ No newline at end of file
diff --git a/src/App.tsx b/src/App.tsx
index c6f738d..82bacfd 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -11,6 +11,7 @@ import AthleticSection from "./components/AthleticsSection";
import EntrepreuneurshipSection from "./components/EntrepreneurshipSection";
import ReviewSection from "./components/ReviewSection";
import Footer from "./components/Footer";
+import { isFirebaseConfigured } from "./firebase";
const App = () => {
const { theme, toggleTheme } = useTheme();
@@ -23,11 +24,34 @@ const App = () => {
text: "",
});
+ const formatFirebaseError = (error: unknown): string => {
+ if (error && typeof error === "object") {
+ const maybeCode = (error as { code?: unknown }).code;
+ const maybeMessage = (error as { message?: unknown }).message;
+ const code = typeof maybeCode === "string" ? maybeCode : undefined;
+ const message = typeof maybeMessage === "string" ? maybeMessage : undefined;
+ if (code && message) return `${code}: ${message}`;
+ if (message) return message;
+ }
+ return String(error);
+ };
+
useEffect(() => {
- fetchReviews().then(setReviews);
+ fetchReviews()
+ .then(setReviews)
+ .catch((error) => {
+ console.error("Error fetching reviews:", error);
+ });
}, []);
const handleAddReview = () => {
+ if (!isFirebaseConfigured) {
+ alert(
+ "Reviews are not configured yet. Add VITE_FIREBASE_* values to a .env/.env.local file and restart `pnpm dev`."
+ );
+ return;
+ }
+
if (
!newReview.name ||
newReview.rating < 1 ||
@@ -42,14 +66,18 @@ const App = () => {
// Add the new review to the state
addReview(newReview)
.then(() => {
- fetchReviews().then(setReviews);
+ fetchReviews()
+ .then(setReviews)
+ .catch((error) => {
+ console.error("Error fetching reviews after add:", error);
+ });
alert("Review added successfully!");
setNewReview({ name: "", rating: -1, position: "", text: "" });
setShowModal(false);
})
.catch((error) => {
console.error("Error adding review:", error);
- alert("Failed to add review. Please try again later.");
+ alert(`Failed to add review: ${formatFirebaseError(error)}`);
setShowModal(false);
});
};
@@ -60,29 +88,26 @@ const App = () => {
.map((_, i) => (
));
};
return (
{/* Theme Toggle Button */}