Merge pull request 'feat(firebase): Add Firebase SDK and initialize Analytics' (#44) from device-token into develop

Reviewed-on: https://repo.ravanertebat.com/TM/tm_panel/pulls/44
Reviewed-by: Sina Nakhostin <s.nakhostin@ravanertebat.com>
This commit is contained in:
Sina Nakhostin
2025-10-04 11:20:05 +03:30
13 changed files with 1147 additions and 23 deletions
+7
View File
@@ -1,2 +1,9 @@
NEXT_PUBLIC_API_URL="https://admin.opplens.com/admin/v1/" NEXT_PUBLIC_API_URL="https://admin.opplens.com/admin/v1/"
NEXT_PUBLIC_TOAST_AUTO_CLOSE_DURATION=2500 NEXT_PUBLIC_TOAST_AUTO_CLOSE_DURATION=2500
NEXT_PUBLIC_FIREBASE_API_KEY=AIzaSyCTjdsk2jE34IfnvSKP1JMTIf_Abd7tbt0
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=opplens-270d1.firebaseapp.com
NEXT_PUBLIC_FIREBASE_PROJECT_ID=opplens-270d1
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=opplens-270d1.firebasestorage.app
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=6923326255
NEXT_PUBLIC_FIREBASE_APP_ID=1:6923326255:web:584a017e8e1bd0e4ed87da
NEXT_PUBLIC_FIREBASE_MEASURMENT_ID=G-VQW40G8VKC
+1016 -7
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -18,6 +18,7 @@
"clsx": "^2.1.1", "clsx": "^2.1.1",
"currency-symbol-map": "^5.1.0", "currency-symbol-map": "^5.1.0",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"firebase": "^12.3.0",
"flatpickr": "^4.6.13", "flatpickr": "^4.6.13",
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
"jsvectormap": "^1.6.0", "jsvectormap": "^1.6.0",
+16
View File
@@ -0,0 +1,16 @@
importScripts("https://www.gstatic.com/firebasejs/8.10.0/firebase-app.js");
importScripts(
"https://www.gstatic.com/firebasejs/8.10.0/firebase-messaging.js",
);
firebase.initializeApp({
apiKey: "AIzaSyCTjdsk2jE34IfnvSKP1JMTIf_Abd7tbt0",
authDomain: "opplens-270d1.firebaseapp.com",
projectId: "opplens-270d1",
storageBucket: "opplens-270d1.firebasestorage.app",
messagingSenderId: "6923326255",
appId: "1:6923326255:web:584a017e8e1bd0e4ed87da",
measurementId: "G-VQW40G8VKC",
});
const messaging = firebase.messaging();
+3
View File
@@ -3,6 +3,7 @@ import "@/css/style.css";
import "react-tooltip/dist/react-tooltip.css"; import "react-tooltip/dist/react-tooltip.css";
import { ConditionalLayout } from "@/components/Layouts/conditional-layout"; import { ConditionalLayout } from "@/components/Layouts/conditional-layout";
import FirebaseProvider from "@/components/Layouts/firebase-provider";
import "flatpickr/dist/flatpickr.min.css"; import "flatpickr/dist/flatpickr.min.css";
import "jsvectormap/dist/jsvectormap.css"; import "jsvectormap/dist/jsvectormap.css";
import type { Metadata } from "next"; import type { Metadata } from "next";
@@ -27,9 +28,11 @@ export default function RootLayout({ children }: PropsWithChildren) {
<html lang="en" suppressHydrationWarning> <html lang="en" suppressHydrationWarning>
<body> <body>
<Providers> <Providers>
<FirebaseProvider>
<NextTopLoader color="#5750F1" showSpinner={false} /> <NextTopLoader color="#5750F1" showSpinner={false} />
<ConditionalLayout>{children}</ConditionalLayout> <ConditionalLayout>{children}</ConditionalLayout>
</FirebaseProvider>
</Providers> </Providers>
</body> </body>
</html> </html>
+8 -4
View File
@@ -1,15 +1,16 @@
"use client"; "use client";
import { PasswordIcon, UserIcon } from "@/assets/icons"; import { PasswordIcon, UserIcon } from "@/assets/icons";
import { FormErrorMessages } from "@/constants/Texts";
import { useLoginQuery } from "@/hooks/queries"; import { useLoginQuery } from "@/hooks/queries";
import { ILoginCredentials } from "@/lib/api"; import { ILoginCredentials } from "@/lib/api";
import { COOKIE_KEYS } from "@/lib/shared/cookies";
import { useIsMutating } from "@tanstack/react-query";
import Cookies from "js-cookie";
import { useState } from "react"; import { useState } from "react";
import { SubmitHandler, useForm } from "react-hook-form"; import { SubmitHandler, useForm } from "react-hook-form";
import InputGroup from "../FormElements/InputGroup"; import InputGroup from "../FormElements/InputGroup";
import { FormErrorMessages } from "@/constants/Texts";
import { useIsMutating } from "@tanstack/react-query";
export default function SigninWithPassword() { export default function SigninWithPassword() {
const isMutating = useIsMutating(); const isMutating = useIsMutating();
const [passwordFieldInputType, setPasswordFieldInputType] = useState< const [passwordFieldInputType, setPasswordFieldInputType] = useState<
@@ -20,7 +21,10 @@ export default function SigninWithPassword() {
const { mutate } = useLoginQuery(reset); const { mutate } = useLoginQuery(reset);
const onSubmit: SubmitHandler<ILoginCredentials> = (data) => { const onSubmit: SubmitHandler<ILoginCredentials> = (data) => {
mutate(data); mutate({
...data,
device_token: Cookies.get(COOKIE_KEYS.device_token) ?? "",
});
}; };
return ( return (
<form onSubmit={handleSubmit(onSubmit)}> <form onSubmit={handleSubmit(onSubmit)}>
@@ -1,11 +1,11 @@
"use client"; "use client";
import { AuthGuard, LoginGuard } from "@/contexts";
import { usePathname } from "next/navigation"; import { usePathname } from "next/navigation";
import type { PropsWithChildren } from "react"; import type { PropsWithChildren } from "react";
import { Header } from "./header"; import { Header } from "./header";
import Notification from "./notification";
import { Sidebar } from "./sidebar"; import { Sidebar } from "./sidebar";
import { AuthGuard, LoginGuard } from "@/contexts";
export function ConditionalLayout({ children }: PropsWithChildren) { export function ConditionalLayout({ children }: PropsWithChildren) {
const pathname = usePathname(); const pathname = usePathname();
const isAuthPage = pathname.startsWith("/auth"); const isAuthPage = pathname.startsWith("/auth");
@@ -18,6 +18,7 @@ export function ConditionalLayout({ children }: PropsWithChildren) {
</LoginGuard> </LoginGuard>
) : ( ) : (
<AuthGuard> <AuthGuard>
<Notification>
<div className="flex min-h-screen"> <div className="flex min-h-screen">
<Sidebar /> <Sidebar />
<div className="w-full bg-gray-2 dark:bg-[#020d1a]"> <div className="w-full bg-gray-2 dark:bg-[#020d1a]">
@@ -27,6 +28,7 @@ export function ConditionalLayout({ children }: PropsWithChildren) {
</main> </main>
</div> </div>
</div> </div>
</Notification>
</AuthGuard> </AuthGuard>
)} )}
</> </>
@@ -0,0 +1,11 @@
"use client";
import useFirebase from "@/hooks/use-firebase";
import type { PropsWithChildren } from "react";
const FirebaseProvider = ({ children }: PropsWithChildren) => {
useFirebase();
return <>{children}</>;
};
export default FirebaseProvider;
+22
View File
@@ -0,0 +1,22 @@
"use client";
import { onMessage } from "firebase/messaging";
import { useEffect, type PropsWithChildren } from "react";
import { messaging } from "@/services/firebase";
import { toast } from "react-toastify";
const Notification = ({ children }: PropsWithChildren) => {
useEffect(() => {
onMessage(messaging, (payload) => {
console.log("Message received. ", payload);
if (payload.notification) {
toast.success(payload.notification.title ?? "New Notification");
}
});
}, []);
return <>{children}</>;
};
export default Notification;
+12
View File
@@ -0,0 +1,12 @@
"use client";
import { useEffect } from "react";
import { requestForToken } from "../services/firebase";
const useFirebase = () => {
useEffect(() => {
requestForToken();
}, []);
};
export default useFirebase;
+1
View File
@@ -45,6 +45,7 @@ export const CreateAdminSchema = z.object({
export const LoginCredentials = z.object({ export const LoginCredentials = z.object({
username: z.string(), username: z.string(),
password: z.string(), password: z.string(),
device_token: z.string(),
}); });
export const AdminListResponse = z.object({ export const AdminListResponse = z.object({
users: z.array(UserSchema), users: z.array(UserSchema),
+1
View File
@@ -6,4 +6,5 @@ export const COOKIE_KEYS = {
theme: `${_COOKIE_PREFIX}_theme`, theme: `${_COOKIE_PREFIX}_theme`,
refresh_token: `${_COOKIE_PREFIX}_refresh_token`, refresh_token: `${_COOKIE_PREFIX}_refresh_token`,
user: `${_COOKIE_PREFIX}_user`, user: `${_COOKIE_PREFIX}_user`,
device_token: `${_COOKIE_PREFIX}_device_token`,
}; };
+35
View File
@@ -0,0 +1,35 @@
import { COOKIE_KEYS } from "@/lib/shared/cookies";
import { initializeApp } from "firebase/app";
import { getMessaging, getToken } from "firebase/messaging";
import Cookies from "js-cookie";
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,
};
const app = initializeApp(firebaseConfig);
export const messaging = getMessaging(app);
export const requestForToken = () => {
return getToken(messaging)
.then((currentToken) => {
if (currentToken) {
console.log("current token for client: ", currentToken);
Cookies.set(COOKIE_KEYS.device_token, currentToken);
} else {
// Show permission request UI
console.log(
"No registration token available. Request permission to generate one.",
);
// ...
}
})
.catch((err) => {
console.log("An error occurred while retrieving token. ", err);
// ...
});
};