mirror of
https://github.com/fergalmoran/radio-otherway.git
synced 2025-12-22 09:50:29 +00:00
Fix FLUC on initial load
This commit is contained in:
6
.idea/jsLibraryMappings.xml
generated
Normal file
6
.idea/jsLibraryMappings.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="JavaScriptLibraryMappings">
|
||||
<file url="PROJECT" libraries="{theme-change}" />
|
||||
</component>
|
||||
</project>
|
||||
1
.idea/web.iml
generated
1
.idea/web.iml
generated
@@ -11,5 +11,6 @@
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="theme-change" level="application" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -27,6 +27,7 @@
|
||||
"eslint-config-next": "13.1.5",
|
||||
"feather-icons": "^4.29.0",
|
||||
"firebase": "^9.17.1",
|
||||
"firebase-admin": "^11.5.0",
|
||||
"firebase-functions": "^4.2.1",
|
||||
"fireschema": "^4.0.4",
|
||||
"localforage": "^1.10.0",
|
||||
@@ -36,6 +37,8 @@
|
||||
"pino": "^8.11.0",
|
||||
"pino-logflare": "^0.3.12",
|
||||
"react": "18.2.0",
|
||||
"react-daisyui": "^3.0.3",
|
||||
"react-device-detect": "^2.2.3",
|
||||
"react-dom": "18.2.0",
|
||||
"react-dropzone": "^14.2.3",
|
||||
"react-feather": "^2.0.10",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { LoginPage } from "@/components/auth";
|
||||
import { LoginPage } from "@/components/pages/auth";
|
||||
import React from "react";
|
||||
|
||||
const Login = async () => {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import React from "react";
|
||||
import { SignupPage } from "@/components/pages/auth";
|
||||
|
||||
const Signup = async () => {
|
||||
return <h1>Coming soon</h1>;
|
||||
return <div className="flex flex-wrap w-full justify-evenly"><SignupPage /></div>;
|
||||
};
|
||||
|
||||
export default Signup;
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import React from "react";
|
||||
|
||||
export default function Head() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<title>Radio::Otherway</title>
|
||||
@@ -27,6 +30,8 @@ export default function Head() {
|
||||
/>
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5" />
|
||||
{/*need to include this here to avoid the FLUC on initial load*/}
|
||||
<script src="https://unpkg.com/theme-change@2.2.0/index.js" />
|
||||
<meta name="msapplication-TileColor" content="#da532c" />
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
</>
|
||||
|
||||
@@ -4,41 +4,38 @@ import "./globals.css";
|
||||
import { Raleway } from "@next/font/google";
|
||||
import { NavBar, PushNotificationWrapper } from "@/components/layout";
|
||||
import { AuthUserProvider } from "@/lib/auth/authUserContext";
|
||||
import { Toaster } from "react-hot-toast";
|
||||
import { defaults } from "@/lib/constants";
|
||||
import { themeChange } from "theme-change";
|
||||
|
||||
const font = Raleway({
|
||||
weight: ["400", "700"],
|
||||
subsets: ["latin"],
|
||||
variable: "--font-raleway",
|
||||
variable: "--font-raleway"
|
||||
});
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
React.useEffect(() => {
|
||||
const theme = localStorage.getItem("theme") || defaults.defaultTheme;
|
||||
if (theme && !document.body.dataset.theme) {
|
||||
document.body.dataset.theme = theme;
|
||||
}
|
||||
themeChange(false);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<html lang="en">
|
||||
<head />
|
||||
<body className={`${font.className}`}>
|
||||
<PushNotificationWrapper>
|
||||
<AuthUserProvider>
|
||||
<div className="flex flex-col min-h-screen bg-base-100">
|
||||
<NavBar />
|
||||
<div className="items-end grow place-items-center bg-base-200 text-primary-content">
|
||||
<main className=" text-base-content">{children}</main>
|
||||
</div>
|
||||
</div>
|
||||
</AuthUserProvider>
|
||||
</PushNotificationWrapper>
|
||||
</body>
|
||||
<head />
|
||||
<body className={`${font.className}`}>
|
||||
<AuthUserProvider>
|
||||
<PushNotificationWrapper>
|
||||
<div className="flex flex-col min-h-screen bg-base-100">
|
||||
<NavBar />
|
||||
<div className="items-end grow place-items-center bg-base-200 text-primary-content">
|
||||
<main className=" text-base-content">{children}</main>
|
||||
</div>
|
||||
</div>
|
||||
</PushNotificationWrapper>
|
||||
</AuthUserProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
import LoginPage from "./LoginPage";
|
||||
|
||||
export { LoginPage };
|
||||
@@ -38,9 +38,9 @@ const Navbar = () => {
|
||||
<Link
|
||||
href="/signup"
|
||||
id="signup"
|
||||
className="font-normal normal-case font-body btn-primary btn-sm btn"
|
||||
className="gap-1 normal-case btn-ghost btn"
|
||||
>
|
||||
<PlusSquare size={12} className="mr-2" />
|
||||
<PlusSquare size={20} className="inline-block w-5 h-5 stroke-current md:h-6 md:w-6" />
|
||||
Register
|
||||
</Link>
|
||||
<Link
|
||||
@@ -62,10 +62,10 @@ const Navbar = () => {
|
||||
<Link href="/">
|
||||
<Image src="/logo.png" alt="Otherway" width={42} height={42} />
|
||||
</Link>
|
||||
<div className="flex-col hidden ml-auto text-sm text-center font-body md:flex md:flex-row md:space-x-10">
|
||||
<div className="flex-col hidden ml-auto text-sm text-center font-body md:flex md:flex-row">
|
||||
{!loading && NavMenu}
|
||||
<ThemeSelector />
|
||||
</div>
|
||||
<ThemeSelector />
|
||||
|
||||
<div className="ml-auto lg:hidden">
|
||||
<div className="dropdown-end dropdown" data-cy="dropdown">
|
||||
|
||||
@@ -6,58 +6,92 @@ import { Toaster } from "react-hot-toast";
|
||||
import { getMessaging, onMessage } from "firebase/messaging";
|
||||
import { app } from "@/lib/auth/firebase";
|
||||
import ToastService from "../widgets/toast";
|
||||
import { doc, setDoc } from "@firebase/firestore";
|
||||
import { useAuthUserContext } from "@/lib/auth/authUserContext";
|
||||
import { users } from "@/lib/db";
|
||||
import logger from "@/lib/util/logging";
|
||||
import { parseUserAgent } from "react-device-detect";
|
||||
|
||||
const PushNotificationWrapper = ({ children }: React.PropsWithChildren) => {
|
||||
const router = useRouter();
|
||||
useEffect(() => {
|
||||
setToken();
|
||||
const router = useRouter();
|
||||
const { profile } = useAuthUserContext();
|
||||
useEffect(() => {
|
||||
const _getAndStoreRegistrationToken = async () => {
|
||||
const { ua } = parseUserAgent(window.navigator.userAgent);
|
||||
|
||||
// Event listener that listens for the push notification event in the background
|
||||
if ("serviceWorker" in navigator) {
|
||||
navigator.serviceWorker.addEventListener("message", (event) => {
|
||||
console.log("event for the service worker", event);
|
||||
if (!profile) return;
|
||||
await setToken();
|
||||
|
||||
// Event listener that listens for the push notification event in the background
|
||||
if ("serviceWorker" in navigator) {
|
||||
navigator.serviceWorker.addEventListener("message", (event) => {
|
||||
console.log("event for the service worker", event);
|
||||
});
|
||||
}
|
||||
|
||||
// Calls the getMessage() function if the token is there
|
||||
async function setToken() {
|
||||
try {
|
||||
if (!profile) return;
|
||||
const token = await firebaseCloudMessaging.init();
|
||||
if (token && profile) {
|
||||
const newRegistration = {
|
||||
fcmToken: token,
|
||||
deviceType: ua,
|
||||
lastSeen: new Date()
|
||||
};
|
||||
const index = profile.deviceRegistrations?.findIndex(reg => {
|
||||
return reg.fcmToken === token;
|
||||
});
|
||||
if (index !== undefined && index !== -1) {
|
||||
if (profile.deviceRegistrations && profile.deviceRegistrations[index]) {
|
||||
profile.deviceRegistrations[index] = newRegistration;
|
||||
}
|
||||
} else {
|
||||
profile.deviceRegistrations?.push(newRegistration);
|
||||
}
|
||||
}
|
||||
const profileWithRegistrations = Object.assign({}, profile);
|
||||
await setDoc(doc(users, profile?.id), profileWithRegistrations, { merge: true });
|
||||
getMessage();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
};
|
||||
_getAndStoreRegistrationToken()
|
||||
.catch(err => {
|
||||
logger.error("PushNotificationWrapper", "_getAndStoreRegistrationToken_error", err);
|
||||
});
|
||||
}, [profile]);
|
||||
|
||||
function getMessage() {
|
||||
const messaging = getMessaging(app);
|
||||
onMessage(messaging, (message) => {
|
||||
ToastService.custom(
|
||||
<div
|
||||
onClick={() =>
|
||||
message?.data?.url &&
|
||||
handleClickPushNotification(message?.data?.url)
|
||||
}
|
||||
>
|
||||
<h5>{message?.notification?.title}</h5>
|
||||
<h6>{message?.notification?.body}</h6>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Calls the getMessage() function if the token is there
|
||||
async function setToken() {
|
||||
try {
|
||||
const token = await firebaseCloudMessaging.init();
|
||||
if (token) {
|
||||
console.log("token", token);
|
||||
getMessage();
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
});
|
||||
function getMessage() {
|
||||
const messaging = getMessaging(app);
|
||||
|
||||
onMessage(messaging, (message) => {
|
||||
ToastService.custom(
|
||||
<div
|
||||
onClick={() =>
|
||||
message?.data?.url &&
|
||||
handleClickPushNotification(message?.data?.url)
|
||||
}
|
||||
>
|
||||
<h5>{message?.notification?.title}</h5>
|
||||
<h6>{message?.notification?.body}</h6>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
}
|
||||
const handleClickPushNotification = (url: string) => {
|
||||
router.push(url);
|
||||
};
|
||||
return (
|
||||
<>
|
||||
const handleClickPushNotification = (url: string) => {
|
||||
router.push(url);
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<Toaster />
|
||||
{children}
|
||||
{children}
|
||||
</>
|
||||
);
|
||||
};
|
||||
);
|
||||
}
|
||||
;
|
||||
|
||||
export default PushNotificationWrapper;
|
||||
|
||||
157
src/components/pages/auth/SignupPage.tsx
Normal file
157
src/components/pages/auth/SignupPage.tsx
Normal file
@@ -0,0 +1,157 @@
|
||||
"use client";
|
||||
import React from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import useFirebaseAuth from "@/lib/auth/useFirebaseAuth";
|
||||
import { IoLogoFacebook, IoLogoGoogle, IoLogoTwitter } from "react-icons/io";
|
||||
|
||||
const SignupPage = () => {
|
||||
const { signInWithGoogle, signInWithFacebook, signInWithTwitter, profile, signUp } =
|
||||
useFirebaseAuth();
|
||||
const router = useRouter();
|
||||
const [error, setError] = React.useState("");
|
||||
const [email, setEmail] = React.useState("");
|
||||
const [password, setPassword] = React.useState("");
|
||||
const [confirmPassword, setConfirmPassword] = React.useState("");
|
||||
|
||||
|
||||
const register = async (
|
||||
$event: React.SyntheticEvent<HTMLButtonElement>
|
||||
): Promise<void> => {
|
||||
$event.preventDefault();
|
||||
const result = await signUp(email, password);
|
||||
if (result === "auth/email-already-in-use") {
|
||||
setError("This email address has already been used to create an account.");
|
||||
} else if (result === "auth/invalid-email") {
|
||||
setError("Please enter a correct email address");
|
||||
} else {
|
||||
setError("Unable to create an account for you at this time");
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div className="max-w-lg p-10 rounded-md shadow-md font-body bg-base-100 text-base-content md:flex-1">
|
||||
<h3 className="my-4 text-2xl font-semibold font-title">
|
||||
Create New Account
|
||||
</h3>
|
||||
{error && (
|
||||
<div className="shadow-lg alert alert-error mb-4">
|
||||
<div>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="flex-shrink-0 w-6 h-6 stroke-current" fill="none"
|
||||
viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2"
|
||||
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<span>{error}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<form action="#" className="flex flex-col space-y-5">
|
||||
<div className="flex flex-col space-y-1">
|
||||
<label htmlFor="email" className="text-sm">
|
||||
Email address
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
autoFocus
|
||||
className="input-bordered input-primary input input-sm"
|
||||
value={email}
|
||||
onChange={(event) => {
|
||||
setEmail(event.target.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-1">
|
||||
<div className="flex items-center justify-between">
|
||||
<label htmlFor="password" className="text-sm">
|
||||
Password
|
||||
</label>
|
||||
</div>
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
className="input-bordered input-primary input input-sm"
|
||||
value={password}
|
||||
onChange={(event) => {
|
||||
setPassword(event.target.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-1">
|
||||
<div className="flex items-center justify-between">
|
||||
<label htmlFor="password" className="text-sm">
|
||||
Repeat password
|
||||
</label>
|
||||
</div>
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
className="input-bordered input-primary input input-sm"
|
||||
value={confirmPassword}
|
||||
onChange={(event) => {
|
||||
setConfirmPassword(event.target.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
className="w-full btn-primary btn"
|
||||
onClick={(event) => {
|
||||
void register(event);
|
||||
}}
|
||||
>
|
||||
Register for Account
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col space-y-5">
|
||||
<span className="flex items-center justify-center space-x-2">
|
||||
<span className="h-px bg-gray-400 w-14" />
|
||||
<span className="font-normal text-gray-500">or sign up with</span>
|
||||
<span className="h-px bg-gray-400 w-14" />
|
||||
</span>
|
||||
<div className="flex items-center justify-center gap-4">
|
||||
<button
|
||||
type="button"
|
||||
className="w-1/3 gap-2 btn"
|
||||
onClick={signInWithTwitter}
|
||||
>
|
||||
<div className="text-base-content">
|
||||
<IoLogoTwitter />
|
||||
</div>
|
||||
<span className="text-sm font-medium text-base-content">
|
||||
Twitter
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="w-1/3 gap-2 btn"
|
||||
onClick={signInWithGoogle}
|
||||
>
|
||||
<div className="text-base-content">
|
||||
<IoLogoGoogle />
|
||||
</div>
|
||||
<span className="text-sm font-medium text-base-content">
|
||||
Gmail
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="w-1/3 gap-2 btn"
|
||||
onClick={signInWithFacebook}
|
||||
>
|
||||
<div className="text-base-content">
|
||||
<IoLogoFacebook />
|
||||
</div>
|
||||
<span className="text-sm font-medium text-base-content">
|
||||
Facebook
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SignupPage;
|
||||
4
src/components/pages/auth/index.ts
Normal file
4
src/components/pages/auth/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import LoginPage from "./LoginPage";
|
||||
import SignupPage from "./SignupPage";
|
||||
|
||||
export { LoginPage, SignupPage };
|
||||
@@ -19,8 +19,7 @@ const ProfilePageComponentProfile = () => {
|
||||
const [url, setUrl] = React.useState("");
|
||||
const [image, setImage] = React.useState("");
|
||||
React.useEffect(() => {
|
||||
console.log("ProfilePageComponentProfile", "useEffect", profile);
|
||||
if (profile) {
|
||||
if (profile) {
|
||||
setEmail(profile.email as string);
|
||||
setDisplayName(profile.displayName as string);
|
||||
setAbout(profile.about as string);
|
||||
|
||||
@@ -5,21 +5,11 @@ import { useState, useEffect } from "react";
|
||||
import { IoColorPaletteOutline } from "react-icons/io5";
|
||||
import { BiDownArrow } from "react-icons/bi";
|
||||
import { defaults } from "@/lib/constants";
|
||||
import { useTheme } from "react-daisyui";
|
||||
import { themeChange } from "theme-change";
|
||||
|
||||
const ThemeSelector = () => {
|
||||
const [activeTheme, setActiveTheme] = useState(
|
||||
typeof window !== "undefined" && localStorage.getItem("theme") ||
|
||||
defaults.defaultTheme);
|
||||
//
|
||||
useEffect(() => {
|
||||
if (document) {
|
||||
document.body.dataset.theme = activeTheme;
|
||||
window.localStorage.setItem("theme", activeTheme);
|
||||
}
|
||||
}, [activeTheme]);
|
||||
|
||||
const _switchTheme = useCallback((theme: string) => {
|
||||
setActiveTheme(theme);
|
||||
const _switchTheme = useCallback(() => {
|
||||
const elem = document.activeElement as HTMLElement;
|
||||
elem?.blur();
|
||||
}, []);
|
||||
@@ -39,7 +29,8 @@ const ThemeSelector = () => {
|
||||
<button
|
||||
key={theme.id}
|
||||
className="overflow-hidden text-left rounded-lg outline-base-content"
|
||||
onClick={() => _switchTheme(theme.id)}
|
||||
data-set-theme={theme.id}
|
||||
onClick={() => _switchTheme()}
|
||||
>
|
||||
<div
|
||||
data-theme={theme.id}
|
||||
|
||||
8
src/lib/auth/firebaseAdmin.tsx
Normal file
8
src/lib/auth/firebaseAdmin.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
import admin from "firebase-admin";
|
||||
|
||||
const serviceAccount = JSON.parse(
|
||||
process.env.FIREBASE_SERVICE_ACCOUNT_KEY as string
|
||||
);
|
||||
export const firebaseAdmin = admin.initializeApp({
|
||||
credential: admin.credential.cert(serviceAccount)
|
||||
});
|
||||
@@ -3,16 +3,16 @@ import localforage from "localforage";
|
||||
import { getMessaging, getToken } from "firebase/messaging";
|
||||
import { app } from "./firebase";
|
||||
|
||||
|
||||
const firebaseCloudMessaging = {
|
||||
init: async () => {
|
||||
init: async (): Promise<string | null> => {
|
||||
try {
|
||||
const messaging = getMessaging(app);
|
||||
|
||||
const tokenInLocalForage = await localforage.getItem("fcm_token");
|
||||
|
||||
// Return the token if it is alredy in our local storage
|
||||
if (tokenInLocalForage !== null) {
|
||||
return tokenInLocalForage;
|
||||
return tokenInLocalForage as string;
|
||||
}
|
||||
|
||||
// Request the push notification permission from browser
|
||||
@@ -23,14 +23,14 @@ const firebaseCloudMessaging = {
|
||||
|
||||
// Set token in our local storage
|
||||
if (token) {
|
||||
localforage.setItem("fcm_token", token);
|
||||
await localforage.setItem("fcm_token", token);
|
||||
return token;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
return null;
|
||||
}
|
||||
};
|
||||
export { firebaseCloudMessaging };
|
||||
|
||||
@@ -19,6 +19,7 @@ import { Profile } from "@/models";
|
||||
import { users } from "../db";
|
||||
import { doc, getDoc, setDoc } from "firebase/firestore";
|
||||
import { servicesVersion } from "@ts-morph/common/lib/typescript";
|
||||
import logger from "../util/logging";
|
||||
|
||||
export default function useFirebaseAuth() {
|
||||
const [profile, setProfile] = useState<Profile | undefined>();
|
||||
@@ -38,7 +39,9 @@ export default function useFirebaseAuth() {
|
||||
(savedProfile?.email || auth.currentUser.email) as string,
|
||||
(savedProfile?.displayName || auth.currentUser.email) as string,
|
||||
(savedProfile?.photoURL || auth.currentUser.email) as string,
|
||||
savedProfile?.about as string
|
||||
savedProfile?.about as string,
|
||||
new Date(),
|
||||
savedProfile?.deviceRegistrations
|
||||
);
|
||||
setProfile(profile);
|
||||
await setDoc(doc(users, auth.currentUser.uid), Object.assign({}, profile), {
|
||||
@@ -65,8 +68,16 @@ export default function useFirebaseAuth() {
|
||||
const signIn = (email: string, password: string) =>
|
||||
signInWithEmailAndPassword(auth, email, password);
|
||||
|
||||
const signUp = (email: string, password: string) =>
|
||||
createUserWithEmailAndPassword(auth, email, password);
|
||||
const signUp = async (email: string, password: string): Promise<string> => {
|
||||
try {
|
||||
const response = await createUserWithEmailAndPassword(auth, email, password);
|
||||
logger.debug("useFireBaseAuth", "signUp_success", response);
|
||||
return "";
|
||||
} catch (err: { code: string } | any) {
|
||||
logger.error("useFireBaseAuth", "signUp", err);
|
||||
return err.code;
|
||||
}
|
||||
};
|
||||
|
||||
const logOut = () => signOut(auth).then(clear);
|
||||
|
||||
|
||||
5
src/models/deviceregistration.ts
Normal file
5
src/models/deviceregistration.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export default interface DeviceRegistration {
|
||||
deviceType: string;
|
||||
fcmToken: string;
|
||||
lastSeen: Date;
|
||||
};
|
||||
@@ -2,6 +2,7 @@ import Profile from "./profile";
|
||||
import Show from "./show";
|
||||
import Reminder from "./reminder";
|
||||
import Notification from "./notification";
|
||||
import DeviceRegistration from "./deviceregistration";
|
||||
import type { RemindersProcessed } from "./processes";
|
||||
|
||||
export {
|
||||
@@ -9,5 +10,6 @@ export {
|
||||
Show,
|
||||
Reminder,
|
||||
Notification,
|
||||
type DeviceRegistration,
|
||||
RemindersProcessed
|
||||
};
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import DeviceRegistration from "@/models/deviceregistration";
|
||||
|
||||
export default class Profile {
|
||||
id: string;
|
||||
email: string | null;
|
||||
@@ -6,6 +8,7 @@ export default class Profile {
|
||||
emailVerified: boolean = false;
|
||||
about?: String;
|
||||
lastSeen: Date;
|
||||
deviceRegistrations?: DeviceRegistration[] = [];
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
@@ -13,7 +16,8 @@ export default class Profile {
|
||||
displayName: string | null,
|
||||
photoURL: string | null,
|
||||
about?: string,
|
||||
lastSeen?: Date
|
||||
lastSeen?: Date,
|
||||
deviceRegistrations?: DeviceRegistration[]
|
||||
) {
|
||||
this.id = id;
|
||||
this.email = email;
|
||||
@@ -22,5 +26,6 @@ export default class Profile {
|
||||
this.about = about || "";
|
||||
|
||||
this.lastSeen = lastSeen || new Date();
|
||||
this.deviceRegistrations = deviceRegistrations || this.deviceRegistrations;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
import { getMessaging } from "firebase/messaging";
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const message = {
|
||||
data: {
|
||||
score: "850",
|
||||
time: "2:45",
|
||||
},
|
||||
token:
|
||||
"eQM4lB5_AWPgTPfIqdJ4jQ:APA91bHwBiljwSbyx7gU_IPLu59hFPkCPd-0OElLNQyF4m6nBluXfBnyvh115tpSjR0ceO9IhY7PuwkoR09u_JykBmjBvrsuUkDWzOdKxVLnV5STOV98dr56Avo5HdmPrei6SxMbHeTb",
|
||||
};
|
||||
|
||||
// Send a message to the device corresponding to the provided
|
||||
// registration token.
|
||||
getMessaging()
|
||||
.send(message)
|
||||
.then((response) => {
|
||||
console.log("Successfully sent message:", response);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error sending message:", error);
|
||||
});
|
||||
};
|
||||
47
src/pages/api/debug/fcm/index.ts
Normal file
47
src/pages/api/debug/fcm/index.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
import logger from "@/lib/util/logging";
|
||||
import { firebaseAdmin } from "@/lib/auth/firebaseAdmin";
|
||||
import { doc, getDocs, query, where } from "@firebase/firestore";
|
||||
import { getDoc } from "firebase/firestore";
|
||||
import { users } from "@/lib/db";
|
||||
import { Profile, Reminder } from "@/models";
|
||||
|
||||
const _getPayload = (message: string) => ({
|
||||
notification: {
|
||||
title: "Argle Bargle",
|
||||
body: `You are a ${message}`,
|
||||
image: "https://otherway.fergl.ie/logo.png"
|
||||
},
|
||||
data: {
|
||||
url: "https://google.com"
|
||||
}
|
||||
});
|
||||
|
||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
|
||||
|
||||
// Send a message to the device corresponding to the provided
|
||||
// registration token.
|
||||
try {
|
||||
const results: any[] = [];
|
||||
const userResultSet = await getDocs(query(users, where("email", "==", "fergal.moran@gmail.com")));
|
||||
for (let u of userResultSet.docs) {
|
||||
const user = u.data() as Profile;
|
||||
if (user?.deviceRegistrations) {
|
||||
for (const token of user?.deviceRegistrations) {
|
||||
results.push(await firebaseAdmin
|
||||
.messaging()
|
||||
.sendToDevice(
|
||||
token.fcmToken,
|
||||
_getPayload(token.deviceType)));
|
||||
}
|
||||
}
|
||||
}
|
||||
res.status(200).json({ results });
|
||||
} catch (err) {
|
||||
logger.error("fcm", "sendPush", "Failed", err);
|
||||
res.status(500);
|
||||
}
|
||||
res.end();
|
||||
};
|
||||
export default handler;
|
||||
@@ -2,7 +2,6 @@ import { NextApiRequest, NextApiResponse } from "next";
|
||||
import { setupCalendarWebhook } from "@/lib/util/google/calendarWatcher";
|
||||
|
||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
await setupCalendarWebhook();
|
||||
res.status(200).json({ hello: process.env });
|
||||
};
|
||||
export default handler;
|
||||
Reference in New Issue
Block a user