diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx
new file mode 100644
index 0000000..6dd1253
--- /dev/null
+++ b/app/dashboard/page.tsx
@@ -0,0 +1,9 @@
+"use client";
+
+import withAdmin from "@/lib/withAdmin";
+
+function DashboardPage() {
+ return
Welcome to the admin dashboard!
;
+}
+
+export default withAdmin(DashboardPage);
diff --git a/app/db/index.ts b/app/db/index.ts
index 23c29c7..b469c1e 100644
--- a/app/db/index.ts
+++ b/app/db/index.ts
@@ -1,7 +1,8 @@
import { getFirestore } from "firebase/firestore";
+import { getAuth } from "firebase/auth";
import fireapp from "@/app/firebase.config";
// Get Firestore instance
const db = getFirestore(fireapp);
-
+export const auth = getAuth(fireapp);
export { db };
diff --git a/app/firebase.config.ts b/app/firebase.config.ts
index 08ebff2..f0153ea 100644
--- a/app/firebase.config.ts
+++ b/app/firebase.config.ts
@@ -13,3 +13,4 @@ const firebaseConfig = {
const fireapp = initializeApp(firebaseConfig);
export default fireapp;
+
\ No newline at end of file
diff --git a/app/globals.css b/app/globals.css
index 6b717ad..e69de29 100644
--- a/app/globals.css
+++ b/app/globals.css
@@ -1,21 +0,0 @@
-@tailwind base;
-@tailwind components;
-@tailwind utilities;
-
-:root {
- --background: #ffffff;
- --foreground: #171717;
-}
-
-@media (prefers-color-scheme: dark) {
- :root {
- --background: #0a0a0a;
- --foreground: #ededed;
- }
-}
-
-body {
- color: var(--foreground);
- background: var(--background);
- font-family: Arial, Helvetica, sans-serif;
-}
diff --git a/app/layout.tsx b/app/layout.tsx
index f7fa87e..0f6ec39 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -1,33 +1,17 @@
-import type { Metadata } from "next";
-import { Geist, Geist_Mono } from "next/font/google";
+"use client";
+import React from 'react'
+import { SessionProvider } from "next-auth/react";
import "./globals.css";
-const geistSans = Geist({
- variable: "--font-geist-sans",
- subsets: ["latin"],
-});
-
-const geistMono = Geist_Mono({
- variable: "--font-geist-mono",
- subsets: ["latin"],
-});
-
-export const metadata: Metadata = {
- title: "Create Next App",
- description: "Generated by create next app",
-};
-
export default function RootLayout({
children,
-}: Readonly<{
+}: {
children: React.ReactNode;
-}>) {
+}) {
return (
-
- {children}
+
+ {children}
);
diff --git a/app/login/page.tsx b/app/login/page.tsx
new file mode 100644
index 0000000..2d1d3c0
--- /dev/null
+++ b/app/login/page.tsx
@@ -0,0 +1,29 @@
+"use client";
+
+import { signIn, signOut, useSession } from "next-auth/react";
+import { useRouter } from "next/navigation";
+
+export default function LoginPage() {
+ const { data: session, status } = useSession();
+ console.log(session);
+ console.log(status);
+ const router = useRouter();
+
+ if (status === "authenticated") {
+ router.push("/dashboard");
+ return null;
+ }
+
+ return (
+
+ {status === "loading" ? (
+
Loading...=
+ ) : (
+ <>
+
Please log in:
+
+ >
+ )}
+
+ );
+}
diff --git a/app/page.tsx b/app/page.tsx
deleted file mode 100644
index 9007252..0000000
--- a/app/page.tsx
+++ /dev/null
@@ -1,101 +0,0 @@
-import Image from "next/image";
-
-export default function Home() {
- return (
-
-
-
-
- -
- Get started by editing{" "}
-
- app/page.tsx
-
- .
-
- - Save and see your changes instantly.
-
-
-
-
-
-
- );
-}
diff --git a/lib/withAdmin.tsx b/lib/withAdmin.tsx
new file mode 100644
index 0000000..d6f786c
--- /dev/null
+++ b/lib/withAdmin.tsx
@@ -0,0 +1,57 @@
+"use client";
+
+import { useEffect, useState } from "react";
+import { db } from "@/app/db";
+import { collection, query, where, doc, getDoc } from "firebase/firestore";
+import { useSession } from "next-auth/react";
+import { useRouter } from "next/navigation";
+
+export default function withAdmin(Component: React.FC) {
+
+ return function AdminProtectedPage(props: any) {
+ const { data: session, status } = useSession();
+ const [isAdmin, setIsAdmin] = useState(false);
+ const [loading, setLoading] = useState(true);
+ const router = useRouter();
+
+ useEffect(() => {
+ const checkAdmin = async () => {
+ if (status === "authenticated" && session!==null) {
+ const userEmail = session.user?.email;
+
+ if (userEmail) {
+ try {
+ const adminQuery =doc(db,"admin",userEmail);
+
+ const querySnapshot = await getDoc(adminQuery);
+ if (querySnapshot.exists()) {
+ setIsAdmin(true);
+ } else {
+ console.log(querySnapshot);
+ alert("You do not have admin access.");
+ router.push("/login");
+ }
+ } catch (error) {
+ console.error("Error checking admin access:", error);
+ router.push("/login");
+ }
+ } else {
+ router.push("/login");
+ }
+ } else if (status === "unauthenticated") {
+ router.push("/login");
+ }
+
+ setLoading(false);
+ };
+
+ checkAdmin();
+ }, [session, status, router]);
+
+ if (loading) {
+ return Loading...
;
+ }
+
+ return isAdmin ? : null;
+ };
+}
diff --git a/package-lock.json b/package-lock.json
index 4464452..ac9aa14 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,11 +8,14 @@
"name": "admin-panel",
"version": "0.1.0",
"dependencies": {
+ "@types/next-auth": "^3.13.0",
"axios": "^1.7.9",
"firebase": "^11.1.0",
"next": "15.1.3",
+ "next-auth": "^4.24.11",
"react": "^19.0.0",
- "react-dom": "^19.0.0"
+ "react-dom": "^19.0.0",
+ "react-firebase-hooks": "^5.1.1"
},
"devDependencies": {
"@eslint/eslintrc": "^3",
@@ -39,6 +42,18 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/@babel/runtime": {
+ "version": "7.26.0",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz",
+ "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==",
+ "license": "MIT",
+ "dependencies": {
+ "regenerator-runtime": "^0.14.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"node_modules/@emnapi/runtime": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz",
@@ -1503,6 +1518,15 @@
"node": ">=12.4.0"
}
},
+ "node_modules/@panva/hkdf": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.2.1.tgz",
+ "integrity": "sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/panva"
+ }
+ },
"node_modules/@pkgjs/parseargs": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
@@ -1628,6 +1652,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/next-auth": {
+ "version": "3.15.0",
+ "resolved": "https://registry.npmjs.org/@types/next-auth/-/next-auth-3.15.0.tgz",
+ "integrity": "sha512-ZVfejlu81YiIRX1m0iKAfvZ3nK7K9EyZWhNARNKsFop8kNAgEvMnlKpTpwN59xkK2OhyWLagPuiDAVBYSO9jSA==",
+ "deprecated": "This is a stub types definition. next-auth provides its own type definitions, so you do not need this installed.",
+ "license": "MIT",
+ "dependencies": {
+ "next-auth": "*"
+ }
+ },
"node_modules/@types/node": {
"version": "20.17.11",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.11.tgz",
@@ -2573,6 +2607,15 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/cookie": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -4610,6 +4653,15 @@
"jiti": "bin/jiti.js"
}
},
+ "node_modules/jose": {
+ "version": "4.15.9",
+ "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz",
+ "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/panva"
+ }
+ },
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -4985,6 +5037,38 @@
}
}
},
+ "node_modules/next-auth": {
+ "version": "4.24.11",
+ "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-4.24.11.tgz",
+ "integrity": "sha512-pCFXzIDQX7xmHFs4KVH4luCjaCbuPRtZ9oBUjUhOk84mZ9WVPf94n87TxYI4rSRf9HmfHEF8Yep3JrYDVOo3Cw==",
+ "license": "ISC",
+ "dependencies": {
+ "@babel/runtime": "^7.20.13",
+ "@panva/hkdf": "^1.0.2",
+ "cookie": "^0.7.0",
+ "jose": "^4.15.5",
+ "oauth": "^0.9.15",
+ "openid-client": "^5.4.0",
+ "preact": "^10.6.3",
+ "preact-render-to-string": "^5.1.19",
+ "uuid": "^8.3.2"
+ },
+ "peerDependencies": {
+ "@auth/core": "0.34.2",
+ "next": "^12.2.5 || ^13 || ^14 || ^15",
+ "nodemailer": "^6.6.5",
+ "react": "^17.0.2 || ^18 || ^19",
+ "react-dom": "^17.0.2 || ^18 || ^19"
+ },
+ "peerDependenciesMeta": {
+ "@auth/core": {
+ "optional": true
+ },
+ "nodemailer": {
+ "optional": true
+ }
+ }
+ },
"node_modules/next/node_modules/postcss": {
"version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
@@ -5023,6 +5107,12 @@
"node": ">=0.10.0"
}
},
+ "node_modules/oauth": {
+ "version": "0.9.15",
+ "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz",
+ "integrity": "sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==",
+ "license": "MIT"
+ },
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@@ -5155,6 +5245,51 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/oidc-token-hash": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.0.3.tgz",
+ "integrity": "sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw==",
+ "license": "MIT",
+ "engines": {
+ "node": "^10.13.0 || >=12.0.0"
+ }
+ },
+ "node_modules/openid-client": {
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.7.1.tgz",
+ "integrity": "sha512-jDBPgSVfTnkIh71Hg9pRvtJc6wTwqjRkN88+gCFtYWrlP4Yx2Dsrow8uPi3qLr/aeymPF3o2+dS+wOpglK04ew==",
+ "license": "MIT",
+ "dependencies": {
+ "jose": "^4.15.9",
+ "lru-cache": "^6.0.0",
+ "object-hash": "^2.2.0",
+ "oidc-token-hash": "^5.0.3"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/panva"
+ }
+ },
+ "node_modules/openid-client/node_modules/lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/openid-client/node_modules/object-hash": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz",
+ "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/optionator": {
"version": "0.9.4",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
@@ -5486,6 +5621,28 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/preact": {
+ "version": "10.25.4",
+ "resolved": "https://registry.npmjs.org/preact/-/preact-10.25.4.tgz",
+ "integrity": "sha512-jLdZDb+Q+odkHJ+MpW/9U5cODzqnB+fy2EiHSZES7ldV5LK7yjlVzTp7R8Xy6W6y75kfK8iWYtFVH7lvjwrCMA==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/preact"
+ }
+ },
+ "node_modules/preact-render-to-string": {
+ "version": "5.2.6",
+ "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.6.tgz",
+ "integrity": "sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw==",
+ "license": "MIT",
+ "dependencies": {
+ "pretty-format": "^3.8.0"
+ },
+ "peerDependencies": {
+ "preact": ">=10"
+ }
+ },
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -5496,6 +5653,12 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/pretty-format": {
+ "version": "3.8.0",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz",
+ "integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==",
+ "license": "MIT"
+ },
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
@@ -5590,6 +5753,16 @@
"react": "^19.0.0"
}
},
+ "node_modules/react-firebase-hooks": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/react-firebase-hooks/-/react-firebase-hooks-5.1.1.tgz",
+ "integrity": "sha512-y2UpWs82xs+39q5Rc/wq316ca52QsC0n8m801V+yM4IC4hbfOL4yQPVSh7w+ydstdvjN9F+lvs1WrO2VYxpmdA==",
+ "license": "Apache-2.0",
+ "peerDependencies": {
+ "firebase": ">= 9.0.0",
+ "react": ">= 16.8.0"
+ }
+ },
"node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
@@ -5643,6 +5816,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/regenerator-runtime": {
+ "version": "0.14.1",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
+ "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
+ "license": "MIT"
+ },
"node_modules/regexp.prototype.flags": {
"version": "1.5.4",
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz",
@@ -6690,6 +6869,15 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/uuid": {
+ "version": "8.3.2",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
+ "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
+ "license": "MIT",
+ "bin": {
+ "uuid": "dist/bin/uuid"
+ }
+ },
"node_modules/websocket-driver": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz",
@@ -6931,6 +7119,12 @@
"node": ">=10"
}
},
+ "node_modules/yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+ "license": "ISC"
+ },
"node_modules/yaml": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz",
diff --git a/package.json b/package.json
index c915cad..ccb4ede 100644
--- a/package.json
+++ b/package.json
@@ -9,11 +9,14 @@
"lint": "next lint"
},
"dependencies": {
+ "@types/next-auth": "^3.13.0",
"axios": "^1.7.9",
"firebase": "^11.1.0",
"next": "15.1.3",
+ "next-auth": "^4.24.11",
"react": "^19.0.0",
- "react-dom": "^19.0.0"
+ "react-dom": "^19.0.0",
+ "react-firebase-hooks": "^5.1.1"
},
"devDependencies": {
"@eslint/eslintrc": "^3",
diff --git a/pages/api/auth/[...nextauth].ts b/pages/api/auth/[...nextauth].ts
new file mode 100644
index 0000000..1567b43
--- /dev/null
+++ b/pages/api/auth/[...nextauth].ts
@@ -0,0 +1,12 @@
+import NextAuth from "next-auth";
+import GoogleProvider from "next-auth/providers/google";
+
+export default NextAuth({
+ providers: [
+ GoogleProvider({
+ clientId: process.env.GOOGLE_CLIENT_ID!,
+ clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
+ }),
+ ],
+ secret: process.env.NEXTAUTH_SECRET,
+});