diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..093af96 --- /dev/null +++ b/.env.example @@ -0,0 +1,9 @@ +# Firebase Configuration +# Copy this file to .env.local and fill in your values + +NEXT_PUBLIC_FIREBASE_API_KEY=your_api_key_here +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=your_sender_id +NEXT_PUBLIC_FIREBASE_APP_ID=your_app_id diff --git a/firebase.js b/firebase.js index 20dd092..0690f8b 100644 --- a/firebase.js +++ b/firebase.js @@ -1,24 +1,22 @@ import { initializeApp } from "firebase/app"; +import { getAuth } from "firebase/auth"; import { getFirestore } from "firebase/firestore"; +// Firebase configuration using environment variables +// For local development, create a .env.local file with these values +// See .env.example for the template 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, - - apiKey: "AIzaSyDovjYqTPUjV7iC_ic5TLgdzYAWyUfbVp4", - authDomain: "my-portfolio-ed76f.firebaseapp.com", - projectId: "my-portfolio-ed76f", - storageBucket: "my-portfolio-ed76f.appspot.com", - messagingSenderId: "499321285862", - appId: "1:499321285862:web:93f5992056d25119841f27", + apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY || "AIzaSyDovjYqTPUjV7iC_ic5TLgdzYAWyUfbVp4", + authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN || "my-portfolio-ed76f.firebaseapp.com", + projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID || "my-portfolio-ed76f", + storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET || "my-portfolio-ed76f.appspot.com", + messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID || "499321285862", + appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID || "1:499321285862:web:93f5992056d25119841f27", }; const app = initializeApp(firebaseConfig); const db = getFirestore(app); +const auth = getAuth(app); -export { app, db }; +export { app, db, auth }; diff --git a/package-lock.json b/package-lock.json index dd5c6b2..99a06a1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,9 @@ "react-dom": "^19.2.1", "react-icons": "^4.10.1", "tailwindcss": "3.3.3", - "typescript": "5.1.6" + "typescript": "5.1.6", + "zod": "^4.2.1", + "zustand": "^5.0.9" }, "devDependencies": { "encoding": "^0.1.13" @@ -5754,6 +5756,44 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.2.1.tgz", + "integrity": "sha512-0wZ1IRqGGhMP76gLqz8EyfBXKk0J2qo2+H3fi4mcUP/KtTocoX08nmIAHl1Z2kJIZbZee8KOpBCSNPRgauucjw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zustand": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.9.tgz", + "integrity": "sha512-ALBtUj0AfjJt3uNRQoL1tL2tMvj6Gp/6e39dnfT6uzpelGru8v1tPOGBzayOWbPJvujM8JojDk3E1LxeFisBNg==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } } } } diff --git a/package.json b/package.json index 3ef2aa5..aad683b 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,9 @@ "react-dom": "^19.2.1", "react-icons": "^4.10.1", "tailwindcss": "3.3.3", - "typescript": "5.1.6" + "typescript": "5.1.6", + "zod": "^4.2.1", + "zustand": "^5.0.9" }, "devDependencies": { "encoding": "^0.1.13" diff --git a/src/app/_layouts/card/card.tsx b/src/app/_layouts/card/card.tsx index 0878f46..a0708f4 100644 --- a/src/app/_layouts/card/card.tsx +++ b/src/app/_layouts/card/card.tsx @@ -6,7 +6,7 @@ export default function Card(
{heading} -
+
{children}
diff --git a/src/app/_layouts/detailedList/detailedList.tsx b/src/app/_layouts/detailedList/detailedList.tsx index 7612658..fd18968 100644 --- a/src/app/_layouts/detailedList/detailedList.tsx +++ b/src/app/_layouts/detailedList/detailedList.tsx @@ -1,53 +1,57 @@ import staticData from "@/app/staticData"; import { DetailedListItem } from "../../models/Item"; -import Card from "../card/card"; import ImagerViewer from "../imagesViewer/ImagersViewer"; import LinePulse from "../pulse/line"; import MultiLinePulse from "../pulse/multiLine"; import { Tags } from "../tags/tags"; import Basic from "../texts/basic"; +import TimelineItem from "../timeline/TimelineItem"; export default function DetailedList( - { title, items, loading = false }: { title: string, items: Array, loading?: boolean }) { - return - }> + { items, loading = false, showDateBadge = true }: { items: Array, loading?: boolean, showDateBadge?: boolean }) { + return <> {loading ? - + + + : items.map((item, i) => ( -
- {i ? -
-
-
- : - <> - } - -
+ + + ))} - + } const ListItemNode = ( - { id, item, loading = false }: { id: any, item: DetailedListItem, loading?: boolean } + { id, item, loading = false, showDateBadge = true }: { id: any, item: DetailedListItem, loading?: boolean, showDateBadge?: boolean } ) => { - return <> -
- {/*
*/} -
-
+ const extractYear = (dateStr: string | undefined): string => { + if (!dateStr) return ""; + if (dateStr === "Present") return "Present"; + const yearMatch = dateStr.match(/\d{4}/); + return yearMatch ? yearMatch[0] : dateStr; + }; + + const startYear = extractYear(item.getStartDate()); + const endYear = extractYear(item.getEndDate()); + const dateText = startYear && endYear && startYear !== endYear + ? `${startYear} - ${endYear}` + : startYear || endYear; + + return ( +
+ {/* Date Badge on Timeline */} + {showDateBadge && dateText && !loading && ( +
+
+ {dateText} +
+
+ )} + +
+
@@ -70,7 +74,7 @@ const ListItemNode = ( - + {Object.entries(lk).at(0)?.[0]} @@ -113,7 +117,7 @@ const ListItemNode = ( : item.getDescription() && item.getDescription()!.map((dcp, i) => -

{dcp}

+

{dcp}

) }
@@ -136,5 +140,5 @@ const ListItemNode = (
- + ); } \ No newline at end of file diff --git a/src/app/_layouts/footer/footer.tsx b/src/app/_layouts/footer/footer.tsx index f9fd052..af66699 100644 --- a/src/app/_layouts/footer/footer.tsx +++ b/src/app/_layouts/footer/footer.tsx @@ -16,29 +16,31 @@ export default function Footer( }) { return <> -