Added logging & profile image

This commit is contained in:
Fergal Moran
2023-03-02 02:04:13 +00:00
parent a7c5e3e53f
commit 2184009367
36 changed files with 1075 additions and 565 deletions

View File

@@ -0,0 +1,15 @@
android-chrome-192x192.png,1674667592000,0875127d8f3725504a67bbebdfa564444dacf191b86845454918a9b05ed8b3a1
android-chrome-256x256.png,1674667592000,01a8a8ec16e650134b655a25f7a94b9ca497ba92879c9466990387c22ebbcaa9
apple-touch-icon.png,1674667592000,881fadedab15fe1a7c1e4174cf3ed027af6b0108a3cd4f95a7c096b7e94a5766
browserconfig.xml,1674667592000,9ce44d5f41efc3b07118b339128eeb2380985c8c72b55e77d86e286137be3b4f
favicon-16x16.png,1674667592000,45b499326c559c0a4a81367cc1d5db952a8ac90a31dea35c8f8fa6a324e4f893
favicon-32x32.png,1674667592000,9c9ea32acf0310554cc723ebd9b7fa69e66c29790961698ee7af9d211af3cb0a
favicon.ico,1674667593000,824a2c204e12d0a5d873da41724fe6e8338efab3a4d5d4fb430f92070eac930e
firebase-messaging-sw.js,1677588733520,c2c4a0d1bc2d0816016729907e8cbd52b30cbd87f944383cffae4142564ba6f7
index.html,1677696301408,fc305676e6e69d5f514b023d8a014a1b91ccc76eb8baae43b75812063ce2f39d
logo.png,1676993163860,0875127d8f3725504a67bbebdfa564444dacf191b86845454918a9b05ed8b3a1
mstile-150x150.png,1674667593000,5fd3f47afb80328e82891432c714fddf5422dae49e1bf85fa4b2e57afcd8ad21
safari-pinned-tab.svg,1674667593000,1e1c8219be1c655a9b75d68093070c7ebd0f45892f04641bece262985b8ca088
site.webmanifest,1674667593000,2021d6fe8b196a9cd9e8cc746c38ace194567ec0f58463df25de88a1b2c5cf70
theme.js,1677676883452,2412441b4ed458d32b6545b812f9cdfc39f86fa8d4290b7288c558b8c9f84e2b
img/logo.jpg,1674673878003,62fa6bb035376283e64deeaa8096ff63aabd421daeeb9b0c1cce53396a2d2a80

View File

@@ -0,0 +1,20 @@
# This file was auto-generated by the Firebase CLI
# https://github.com/firebase/firebase-tools
name: Deploy to Firebase Hosting on merge
'on':
push:
branches:
- develop
jobs:
build_and_deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: npm ci && npm run build
- uses: FirebaseExtended/action-hosting-deploy@v0
with:
repoToken: '${{ secrets.GITHUB_TOKEN }}'
firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT_RADIO_OTHERWAY }}'
channelId: live
projectId: radio-otherway

View File

@@ -0,0 +1,17 @@
# This file was auto-generated by the Firebase CLI
# https://github.com/firebase/firebase-tools
name: Deploy to Firebase Hosting on PR
'on': pull_request
jobs:
build_and_preview:
if: '${{ github.event.pull_request.head.repo.full_name == github.repository }}'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: npm ci && npm run build
- uses: FirebaseExtended/action-hosting-deploy@v0
with:
repoToken: '${{ secrets.GITHUB_TOKEN }}'
firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT_RADIO_OTHERWAY }}'
projectId: radio-otherway

View File

@@ -2,5 +2,19 @@
"firestore": {
"rules": "firestore.rules",
"indexes": "firestore.indexes.json"
},
"hosting": {
"public": "public",
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
],
"rewrites": [
{
"source": "**",
"destination": "/index.html"
}
]
}
}

View File

@@ -3,6 +3,9 @@ const nextConfig = {
experimental: {
appDir: true,
},
}
images: {
domains: ["firebasestorage.googleapis.com"],
},
};
module.exports = nextConfig
module.exports = nextConfig;

View File

@@ -3,8 +3,8 @@
"version": "0.1.1",
"private": true,
"scripts": {
"turbo": "NODE_OPTIONS='-r next-logger' next dev --turbo",
"dev": "NODE_OPTIONS='-r next-logger' node ./server.js",
"turbo": "next dev --turbo",
"dev": "node ./server.js",
"debug": "node ./server.js",
"dev-nossl": "next dev",
"build": "next build",
@@ -12,11 +12,13 @@
"lint": "next lint"
},
"dependencies": {
"@babel/plugin-transform-react-display-name": "^7.18.6",
"@headlessui/react": "^1.7.11",
"@next/font": "13.2.0",
"@prisma/client": "^4.9.0",
"@types/feather-icons": "^4.29.1",
"@types/node": "18.14.1",
"@types/logrocket-react": "^3.0.0",
"@types/node": "18.14.2",
"@types/react": "18.0.28",
"@types/react-dom": "18.0.11",
"@upstash/qstash": "^0.3.6",
@@ -24,8 +26,8 @@
"classnames": "^2.3.2",
"daisyui": "^2.49.0",
"encoding": "^0.1.13",
"eslint": "8.32.0",
"eslint-config-next": "^13.2.2",
"eslint": "8.35.0",
"eslint-config-next": "13.2.3",
"feather-icons": "^4.29.0",
"firebase": "^9.17.1",
"firebase-admin": "^11.5.0",
@@ -33,15 +35,16 @@
"fireschema": "^4.0.4",
"http-status-codes": "^2.2.0",
"localforage": "^1.10.0",
"next": "^13.2.2",
"logrocket": "^3.0.1",
"logrocket-react": "^5.0.1",
"next": "13.2.3",
"next-logger": "^3.0.1",
"next-seo": "^5.15.0",
"pino": "^8.11.0",
"pino-logflare": "^0.3.12",
"react": "^18.2.0",
"path-browserify": "^1.0.1",
"react": "18.2.0",
"react-daisyui": "^3.0.3",
"react-device-detect": "^2.2.3",
"react-dom": "^18.2.0",
"react-dom": "18.2.0",
"react-dropzone": "^14.2.3",
"react-feather": "^2.0.10",
"react-hook-form": "^7.43.2",
@@ -50,12 +53,13 @@
"react-phone-number-input": "^3.2.19",
"theme-change": "^2.3.0",
"twilio": "^4.8.0",
"typescript": "4.9.4",
"typescript": "4.9.5",
"zod": "^3.20.6"
},
"packageManager": "yarn@1.22.19",
"devDependencies": {
"@google-cloud/local-auth": "2.1.1",
"@hookform/devtools": "^4.3.0",
"autoprefixer": "^10.4.13",
"googleapis": "111.0.0",
"postcss": "^8.4.21",

89
public/index.html Normal file
View File

@@ -0,0 +1,89 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Welcome to Firebase Hosting</title>
<!-- update the version number as needed -->
<script defer src="/__/firebase/9.17.1/firebase-app-compat.js"></script>
<!-- include only the Firebase features as you need -->
<script defer src="/__/firebase/9.17.1/firebase-auth-compat.js"></script>
<script defer src="/__/firebase/9.17.1/firebase-database-compat.js"></script>
<script defer src="/__/firebase/9.17.1/firebase-firestore-compat.js"></script>
<script defer src="/__/firebase/9.17.1/firebase-functions-compat.js"></script>
<script defer src="/__/firebase/9.17.1/firebase-messaging-compat.js"></script>
<script defer src="/__/firebase/9.17.1/firebase-storage-compat.js"></script>
<script defer src="/__/firebase/9.17.1/firebase-analytics-compat.js"></script>
<script defer src="/__/firebase/9.17.1/firebase-remote-config-compat.js"></script>
<script defer src="/__/firebase/9.17.1/firebase-performance-compat.js"></script>
<!--
initialize the SDK after all desired features are loaded, set useEmulator to false
to avoid connecting the SDK to running emulators.
-->
<script defer src="/__/firebase/init.js?useEmulator=true"></script>
<style media="screen">
body { background: #ECEFF1; color: rgba(0,0,0,0.87); font-family: Roboto, Helvetica, Arial, sans-serif; margin: 0; padding: 0; }
#message { background: white; max-width: 360px; margin: 100px auto 16px; padding: 32px 24px; border-radius: 3px; }
#message h2 { color: #ffa100; font-weight: bold; font-size: 16px; margin: 0 0 8px; }
#message h1 { font-size: 22px; font-weight: 300; color: rgba(0,0,0,0.6); margin: 0 0 16px;}
#message p { line-height: 140%; margin: 16px 0 24px; font-size: 14px; }
#message a { display: block; text-align: center; background: #039be5; text-transform: uppercase; text-decoration: none; color: white; padding: 16px; border-radius: 4px; }
#message, #message a { box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24); }
#load { color: rgba(0,0,0,0.4); text-align: center; font-size: 13px; }
@media (max-width: 600px) {
body, #message { margin-top: 0; background: white; box-shadow: none; }
body { border-top: 16px solid #ffa100; }
}
</style>
</head>
<body>
<div id="message">
<h2>Welcome</h2>
<h1>Firebase Hosting Setup Complete</h1>
<p>You're seeing this because you've successfully setup Firebase Hosting. Now it's time to go build something extraordinary!</p>
<a target="_blank" href="https://firebase.google.com/docs/hosting/">Open Hosting Documentation</a>
</div>
<p id="load">Firebase SDK Loading&hellip;</p>
<script>
document.addEventListener('DOMContentLoaded', function() {
const loadEl = document.querySelector('#load');
// // 🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥
// // The Firebase SDK is initialized and available here!
//
// firebase.auth().onAuthStateChanged(user => { });
// firebase.database().ref('/path/to/ref').on('value', snapshot => { });
// firebase.firestore().doc('/foo/bar').get().then(() => { });
// firebase.functions().httpsCallable('yourFunction')().then(() => { });
// firebase.messaging().requestPermission().then(() => { });
// firebase.storage().ref('/path/to/ref').getDownloadURL().then(() => { });
// firebase.analytics(); // call to activate
// firebase.analytics().logEvent('tutorial_completed');
// firebase.performance(); // call to activate
//
// // 🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥
try {
let app = firebase.app();
let features = [
'auth',
'database',
'firestore',
'functions',
'messaging',
'storage',
'analytics',
'remoteConfig',
'performance',
].filter(feature => typeof app[feature] === 'function');
loadEl.textContent = `Firebase SDK loaded with ${features.join(', ')}`;
} catch (e) {
console.error(e);
loadEl.textContent = 'Error loading the Firebase SDK, check the console.';
}
});
</script>
</body>
</html>

View File

@@ -8,6 +8,12 @@ import { AuthUserProvider } from "@/lib/auth/authUserContext";
import { themeChange } from "theme-change";
import Script from "next/script";
import logger from "@/lib/util/logging";
import { Toaster } from "react-hot-toast";
import { LoggingProvider, ThemeProvider } from "@/components/providers";
const LogRocket = require("logrocket");
const setupLogRocketReact = require("logrocket-react");
// only initialize when in the browser
const font = Raleway({
weight: ["400", "700"],
@@ -24,20 +30,25 @@ const RootLayout = ({ children }: React.PropsWithChildren) => {
return (
<html lang="en">
<head>
<Script src="/theme.js" strategy="beforeInteractive" />
<Script src="/theme.js" />
</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>
<LoggingProvider>
<body className={`${font.className}`}>
<ThemeProvider>
<Toaster />
<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-base-content">
<main className=" text-base-content">{children}</main>
</div>
</div>
</PushNotificationWrapper>
</AuthUserProvider>
</ThemeProvider>
</body>
</LoggingProvider>
</html>
);
};

View File

@@ -1,7 +1,33 @@
import React from "react";
import { useState } from "react";
const LogRocket = require("logrocket");
const setupLogRocketReact = require("logrocket-react");
import packageJson from "../../package.json";
const Loading = () => {
if (process.env.LOGROCKET_ID && window !== undefined) {
LogRocket.init(process.env.LOGROCKET_ID, {
release: packageJson.version,
rootHostname: "radio-otherway.fergl.ie",
console: {
shouldAggregateConsoleErrors: true,
},
network: {
requestSanitizer: (request: any) => {
// if the url contains token 'ignore' it
if (request.url.toLowerCase().indexOf("token") !== -1) {
// ignore the request response pair
return null;
}
// remove Authorization header from logrocket
request.headers.Authorization = undefined;
// otherwise log the request normally
return request;
},
},
});
setupLogRocketReact(LogRocket);
}
return (
<div role="status">

View File

@@ -1,5 +1,11 @@
import React from "react";
import HomePageComponent from "@/components/pages/home";
import LogRocket from "logrocket";
import setupLogRocketReact from "logrocket-react";
export const metadata = {
title: "Radio Otherway",
};
const getData = async () => {
const res = await fetch(

View File

@@ -1,4 +1,3 @@
import { firebaseCloudMessaging } from "@/lib/auth/firebaseMessaging";
import * as firebase from "firebase/app";
import { useRouter } from "next/navigation";
import React, { useEffect } from "react";
@@ -11,87 +10,94 @@ import { useAuthUserContext } from "@/lib/auth/authUserContext";
import { users } from "@/lib/db";
import logger from "@/lib/util/logging";
import { parseUserAgent } from "react-device-detect";
import { firebaseCloudMessaging } from "@/lib/util/notifications/firebaseMessaging";
const PushNotificationWrapper = ({ children }: React.PropsWithChildren) => {
const router = useRouter();
const { profile } = useAuthUserContext();
useEffect(() => {
const _getAndStoreRegistrationToken = async () => {
const { ua } = parseUserAgent(window.navigator.userAgent);
const router = useRouter();
const { profile } = useAuthUserContext();
useEffect(() => {
const _getAndStoreRegistrationToken = async () => {
const { ua } = parseUserAgent(window.navigator.userAgent);
if (!profile) return;
await setToken();
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);
// 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);
});
}, [profile]);
}
function getMessage() {
const messaging = getMessaging(app);
onMessage(messaging, (message) => {
ToastService.custom(
<div
onClick={() =>
message?.data?.url &&
handleClickPushNotification(message?.data?.url)
// 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);
}
>
<h5>{message?.notification?.title}</h5>
<h6>{message?.notification?.body}</h6>
</div>
);
});
}
const handleClickPushNotification = (url: string) => {
router.push(url);
}
const profileWithRegistrations = Object.assign({}, profile);
await setDoc(doc(users, profile?.id), profileWithRegistrations, {
merge: true,
});
getMessage();
} catch (error) {
console.log(error);
}
}
};
return (
<>
<Toaster />
{children}
</>
);
}
;
_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>
);
});
}
const handleClickPushNotification = (url: string) => {
router.push(url);
};
return (
<>
<Toaster />
{children}
</>
);
};
export default PushNotificationWrapper;

View File

@@ -1,19 +1,23 @@
"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";
import { useFirebaseAuth } from "@/lib/auth";
const LoginPage = () => {
const { signInWithGoogle, signInWithFacebook, signInWithTwitter, profile, signIn } =
useFirebaseAuth();
const {
signInWithGoogle,
signInWithFacebook,
signInWithTwitter,
profile,
signIn,
} = useFirebaseAuth();
const router = useRouter();
const [error, setError] = React.useState("");
const [forgot, setForgot] = React.useState(false);
const [email, setEmail] = React.useState("");
const [password, setPassword] = React.useState("");
const login = async (
event: React.SyntheticEvent<HTMLButtonElement>
): Promise<void> => {
@@ -21,9 +25,7 @@ const LoginPage = () => {
};
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">
Account Login
</h3>
<h3 className="my-4 text-2xl font-semibold font-title">Account Login</h3>
<form action="#" className="flex flex-col space-y-5">
<div className="flex flex-col space-y-1">
<label htmlFor="email" className="text-sm">
@@ -122,10 +124,18 @@ const LoginPage = () => {
{error && (
<div className="shadow-lg alert alert-error">
<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
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>

View File

@@ -1,11 +1,11 @@
"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";
import { AiOutlineExclamationCircle } from "react-icons/ai";
import { Info } from "react-feather";
import { useFirebaseAuth } from "@/lib/auth";
const SignupPage = () => {
const {

View File

@@ -57,14 +57,6 @@ const ProfilePageComponent = () => {
if (profile) {
reset(profile);
}
// setValue([
// { displayName: profile?.displayName },
// { email: profile?.email },
// { about: profile?.about },
// { photoURL: profile?.photoURL },
// { headerPhotoURL: profile?.headerPhotoURL },
// { mobileNumber: profile?.mobileNumber },
// ]);
}, [profile, reset]);
const onSubmit: SubmitHandler<ProfileForm> = async (data) => {
console.log(data);
@@ -93,7 +85,7 @@ const ProfilePageComponent = () => {
}
};
React.useEffect(() => {}, [selectedItem]);
useEffect(() => {}, [selectedItem]);
const _getView = () => {
if (loading) {
return <div>Loading</div>;
@@ -140,6 +132,7 @@ const ProfilePageComponent = () => {
<form onSubmit={handleSubmit(onSubmit)}>
{selectedItem === "profile" ? (
<ProfilePageComponentProfile
setValue={setValue}
register={register}
profile={profile}
/>

View File

@@ -1,20 +1,23 @@
"use client";
import { HeadingSubComponent } from "@/components/widgets/text";
import React from "react";
import { UseFormRegister } from "react-hook-form";
import React, { useState } from "react";
import { UseFormRegister, UseFormSetValue } from "react-hook-form";
import { ProfileForm } from "@/components/pages/profile/ProfilePageComponent";
import { Profile } from "@/models";
import InputText from "@/components/widgets/inputs/InputText";
import { FirebaseImageUpload, InputText } from "@/components/widgets/inputs";
import { InputTextArea } from "@/components/widgets/inputs";
interface IProfilePageComponentProfileProps {
register: UseFormRegister<ProfileForm>;
profile: Profile;
register: UseFormRegister<ProfileForm>;
setValue: UseFormSetValue<ProfileForm>;
}
const ProfilePageComponentProfile = ({
register,
profile,
register,
setValue,
}: IProfilePageComponentProfileProps) => {
const [photoURLFile, setPhotoURLFile] = useState("");
return (
<div className="space-y-8 divide-y sm:space-y-5">
<div>
@@ -83,23 +86,15 @@ const ProfilePageComponentProfile = ({
subHeading="Upload a picture to distinguish you from the rest"
/>
<div className="mt-1 sm:col-span-2 sm:mt-0">
<div className="flex items-center">
<span className="w-12 h-12 overflow-hidden rounded-full">
<svg
className="w-full h-full "
fill="currentColor"
viewBox="0 0 24 24"
>
<path d="M24 20.993V24H0v-2.996A14.977 14.977 0 0112.004 15c4.904 0 9.26 2.354 11.996 5.993zM16.002 8.999a4 4 0 11-8 0 4 4 0 018 0z" />
</svg>
</span>
<button
type="button"
className="px-3 py-2 ml-5 text-sm font-medium leading-4 border rounded-md shadow-sm hover: focus:outline-none focus:ring-2 focus:ring-offset-2"
>
Change
</button>
</div>
<FirebaseImageUpload
forType="user"
imageType="avatar"
itemId={profile.id}
imageUrl={profile.photoURL}
controlName="photoURL"
setValue={setValue}
{...register("photoURL")}
/>
</div>
</div>
@@ -109,40 +104,7 @@ const ProfilePageComponentProfile = ({
subHeading="Upload a wide photo for the top of your profile page"
/>
<div className="mt-1 sm:col-span-2 sm:mt-0">
<div className="flex justify-center max-w-lg px-6 pt-5 pb-6 border-2 border-dashed rounded-md">
<div className="space-y-1 text-center">
<svg
className="w-12 h-12 mx-auto "
stroke="currentColor"
fill="none"
viewBox="0 0 48 48"
aria-hidden="true"
>
<path
d="M28 8H12a4 4 0 00-4 4v20m32-12v8m0 0v8a4 4 0 01-4 4H12a4 4 0 01-4-4v-4m32-4l-3.172-3.172a4 4 0 00-5.656 0L28 28M8 32l9.172-9.172a4 4 0 015.656 0L28 28m0 0l4 4m4-24h8m-4-4v8m-12 4h.02"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
<div className="flex text-sm ">
<label
htmlFor="file-upload"
className="relative font-medium rounded-md cursor-pointer te focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-2"
>
<span>Upload a file</span>
<input
id="file-upload"
name="file-upload"
type="file"
className="sr-only"
/>
</label>
<p className="pl-1">or drag and drop</p>
</div>
<p className="text-xs ">PNG, JPG, GIF up to 10MB</p>
</div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,7 @@
import React from "react";
const LoggingProvider = ({ children }: React.PropsWithChildren) => {
return <>{children}</>;
};
export default LoggingProvider;

View File

@@ -0,0 +1,14 @@
import Loading from "@/app/loading";
import { defaults } from "@/lib/constants";
import React, { useEffect } from "react";
const ThemeProvider = ({ children }: React.PropsWithChildren) => {
const [theme, setTheme] = React.useState("");
useEffect(() => {
setTheme(localStorage.getItem("theme") || defaults.defaultTheme);
}, [theme]);
return theme ? <>{children}</> : <Loading />;
};
ThemeProvider.displayName = "ThemeProvider";
export default ThemeProvider;

View File

@@ -0,0 +1,3 @@
import LoggingProvider from "./LoggingProvider";
import ThemeProvider from "./ThemeProvider";
export { LoggingProvider, ThemeProvider };

View File

@@ -1,5 +1,5 @@
"use client";
import useFirebaseAuth from "@/lib/auth/useFirebaseAuth";
import { useFirebaseAuth } from "@/lib/auth";
import { Show } from "@/models";
import React from "react";
import { MdAddAlarm } from "react-icons/md";

View File

@@ -0,0 +1,199 @@
import {
ref as storageRef,
uploadBytesResumable,
getDownloadURL,
} from "firebase/storage";
import { storage } from "@/lib/db";
import Image from "next/image";
import {
useEffect,
useRef,
useState,
forwardRef,
ChangeEventHandler,
} from "react";
var path = require("path");
import { UploadCloud } from "react-feather";
import { getFileExtension } from "@/lib/util/fileUtils";
import ITextInputProps from "@/components/widgets/inputs/props";
import {
Controller,
FieldPath,
UseFormReturn,
UseFormSetValue,
} from "react-hook-form";
import { ProfileForm } from "@/components/pages/profile/ProfilePageComponent";
interface IFirebaseImageUploaderProps {
forType: "user" | "show";
imageType: "avatar" | "profile";
itemId: string;
imageUrl?: string;
controlName: FieldPath<ProfileForm>;
setValue: UseFormSetValue<ProfileForm>;
onChange: ChangeEventHandler<HTMLInputElement>;
onBlur: ChangeEventHandler<HTMLInputElement>;
}
const FirebaseImageUploader = forwardRef<
HTMLInputElement,
IFirebaseImageUploaderProps
>(
(
{
controlName,
forType,
imageType,
itemId,
imageUrl,
setValue,
onChange,
onBlur,
},
ref
) => {
const [file, setFile] = useState<File | null>();
const [filePath, setFilePath] = useState();
const [isUploading, setIsUploading] = useState(false);
const [percent, setPercent] = useState(0);
const fileInput = useRef<HTMLInputElement>(null);
const [error, setError] = useState("");
const _handleChange = (event: React.FormEvent<HTMLInputElement>) => {
const e = event.target as HTMLInputElement;
if (!e.files || e.files.length === 0) {
return;
}
setFile(e.files[0]);
};
useEffect(() => {
const _cleanUp = () => {
setFile(null);
setIsUploading(false);
setPercent(0);
};
const _handleUpload = () => {
if (!file) {
setError("You must choose a file to upload first");
return;
}
console.log("FirebaseImageUploader", "Creating storage refs", storage);
const extension = getFileExtension(file);
const newFilePath = path.join(
"files",
"images",
forType,
imageType,
`${itemId}.${extension}`
);
setFilePath(newFilePath);
console.log("FirebaseImageUploader", "FilePath", newFilePath);
const remoteFileReference = storageRef(storage, newFilePath);
console.log(
"FirebaseImageUploader",
"Created storage refs",
remoteFileReference
);
console.log("FirebaseImageUploader", "Starting upload task");
setIsUploading(true);
const uploadTask = uploadBytesResumable(remoteFileReference, file);
uploadTask.on(
"state_changed",
(snapshot) => {
const percent = Math.round(
(snapshot.bytesTransferred / snapshot.totalBytes) * 100
);
console.log(
"FirebaseImageUploader",
"uploading",
`${percent}% done`
);
setPercent(percent);
},
(err) => {
_cleanUp();
console.log(err);
},
() => {
getDownloadURL(uploadTask.snapshot.ref).then((url) => {
if (controlName) {
setValue(controlName, url);
}
});
_cleanUp();
}
);
};
if (file && !isUploading) {
_handleUpload();
}
}, [controlName, file, forType, imageType, isUploading, itemId, setValue]);
return imageType === "avatar" ? (
<div className="flex flex-col">
<div className="flex items-center space-x-1">
<span className="w-12 h-12 overflow-hidden rounded-full">
{imageUrl ? (
<Image
alt="Existing image"
src={imageUrl}
className="w-full h-full "
width={48}
height={48}
/>
) : (
<svg
className="w-full h-full "
fill="currentColor"
viewBox="0 0 24 24"
>
<path d="M24 20.993V24H0v-2.996A14.977 14.977 0 0112.004 15c4.904 0 9.26 2.354 11.996 5.993zM16.002 8.999a4 4 0 11-8 0 4 4 0 018 0z" />
</svg>
)}
</span>
<button
type="button"
className={`btn-outline btn-primary btn gap-2 ${
isUploading ? "loading" : ""
}`}
onClick={() => fileInput?.current?.click()}
>
<UploadCloud
className="w-6 h-6"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
/>
{!imageUrl ? "Upload image" : "Change image"}
</button>
<input
name={controlName}
placeholder="File input hidden"
className="invisible"
type="file"
accept="image/*"
ref={fileInput}
onBlur={onBlur}
onChange={($event) => {
_handleChange($event);
onChange($event);
}}
/>
</div>
{percent > 0 && (
<span className="block m-1 overflow-hidden text-xs italic text-gray-600 rounded-full">
{`Uploading, ${percent}% done`}
</span>
)}
</div>
) : (
<div>Hello</div>
);
}
);
FirebaseImageUploader.displayName = "FirebaseImageUploader";
export default FirebaseImageUploader;

View File

@@ -1,3 +1,4 @@
/* eslint-disable @next/next/no-img-element */
import React from "react";
import Dropzone, { DropzoneRef, useDropzone } from "react-dropzone";
@@ -25,6 +26,7 @@ const ImageUpload = () => {
<div id="preview">
{acceptedFiles[0] && (
<img
alt="Uploaded image"
className="object-cover"
src={URL.createObjectURL(acceptedFiles[0])}
/>

View File

@@ -2,8 +2,10 @@ import React, { forwardRef } from "react";
import ITextInputProps from "./props";
const InputText = forwardRef<HTMLInputElement, ITextInputProps>(
({ id, type, placeholder, label, showLabel = true, onChange, onBlur }, ref) => {
(
{ id, type, placeholder, label, showLabel = true, onChange, onBlur },
ref
) => {
return (
<React.Fragment>
{showLabel && (
@@ -26,5 +28,3 @@ const InputText = forwardRef<HTMLInputElement, ITextInputProps>(
);
InputText.displayName = "InputTextAreaComponent";
export default InputText;

View File

@@ -1,6 +1,6 @@
import ImageUpload from "./ImageUpload";
import InputText from "./InputText";
import InputTextArea from "./InputTextArea";
import FirebaseImageUpload from "./FirebaseImageUploader";
export { ImageUpload };
export { InputTextArea };
export { InputText, ImageUpload, InputTextArea, FirebaseImageUpload };

View File

@@ -1,6 +1,6 @@
import React from "react";
import { firebaseCloudMessaging } from "@/lib/auth/firebaseMessaging";
import localforage from "localforage";
import { firebaseCloudMessaging } from "@/lib/util/notifications/firebaseMessaging";
const RequestPushNotifications = () => {
const _checkNotifications = async () => {

View File

@@ -1,5 +1,4 @@
import React from "react";
import InputText from "../inputs/InputText";
import { UseFormRegister } from "react-hook-form";
import { ProfileForm } from "@/components/pages/profile/ProfilePageComponent";
import { Profile } from "@/models";

View File

@@ -1,24 +1,25 @@
import React, { createContext, useContext, Context } from "react";
import useFirebaseAuth from "@/lib/auth/useFirebaseAuth";
import { Profile } from "@/models";
import useFirebaseAuth from "./useFirebaseAuth";
interface IAuthUserContext {
loading: boolean;
profile: Profile | undefined,
logOut: () => void
profile: Profile | undefined;
logOut: () => void;
}
const authUserContext = createContext<IAuthUserContext>({
loading: true,
profile: undefined,
logOut: () => {
}
logOut: () => {},
});
export function AuthUserProvider({ children }: { children: React.ReactNode }) {
const { loading, profile, logOut } = useFirebaseAuth();
return (
<authUserContext.Provider value={{ loading, profile, logOut }}>{children}</authUserContext.Provider>
<authUserContext.Provider value={{ loading, profile, logOut }}>
{children}
</authUserContext.Provider>
);
}

3
src/lib/auth/index.ts Normal file
View File

@@ -0,0 +1,3 @@
import useFirebaseAuth from "./useFirebaseAuth";
export { useFirebaseAuth };

View File

@@ -44,8 +44,8 @@ export default function useFirebaseAuth() {
const profile: Profile = new Profile(
auth.currentUser.uid,
(savedProfile?.email || auth.currentUser.email) as string,
(savedProfile?.displayName || auth.currentUser.email) as string,
(savedProfile?.photoURL || auth.currentUser.email) as string,
(savedProfile?.displayName || auth.currentUser.displayName) as string,
(savedProfile?.photoURL || auth.currentUser.photoURL) as string,
savedProfile?.about as string,
savedProfile?.mobileNumber as string,
new Date(),

View File

@@ -1,7 +1,6 @@
import firebase, { getApp, getApps, initializeApp } from "firebase/app";
import { getStorage } from "firebase/storage";
import "firebase/auth";
// import { getFirestore } from "firebase/firestore";
const firebaseConfig = {
apiKey: "AIzaSyDtk_Ym-AZroXsHvQVcdHXYyc_TvgycAWw",
@@ -10,8 +9,9 @@ const firebaseConfig = {
storageBucket: "radio-otherway.appspot.com",
messagingSenderId: "47147490249",
appId: "1:47147490249:web:a84515b3ce1c481826e618",
measurementId: "G-12YB78EZM4"
measurementId: "G-12YB78EZM4",
};
// export default admin.firestore();
export const app = initializeApp(firebaseConfig);
export const storage = getStorage(app);

View File

@@ -8,6 +8,7 @@ import {
QueryDocumentSnapshot,
SnapshotOptions,
Timestamp,
initializeFirestore,
} from "firebase/firestore";
const firebaseConfig = {
@@ -20,6 +21,9 @@ const firebaseConfig = {
measurementId: "G-12YB78EZM4",
};
export const firebaseApp = initializeApp(firebaseConfig);
initializeFirestore(firebaseApp, {
ignoreUndefinedProperties: true,
});
const firestore = getFirestore();
const createCollection = <T = DocumentData>(collectionName: string) => {
return collection(firestore, collectionName) as CollectionReference<T>;
@@ -64,6 +68,7 @@ import {
Profile,
NotificationSchedule,
} from "@/models";
import { storage } from "./firebaseConfig";
// export all your collections
export const users = createCollection<Profile>("users");
@@ -77,6 +82,5 @@ export const notificationSchedules =
export const reminders = createCollection<Reminder>("reminders");
export const remindersProcessed =
createCollection<RemindersProcessed>("reminders");
export default firestore;
export { createCollection };
export { createCollection, firebaseConfig, storage };

View File

@@ -0,0 +1,2 @@
const getFileExtension = (file: File) => file.name.split(".").pop();
export { getFileExtension };

View File

@@ -0,0 +1,89 @@
import LogRocket from "logrocket";
import * as React from "react";
export const logRocketIdentify = (id: string, options?: any) => {
console.debug(`log rocket identify ${id}`);
if (id) {
LogRocket.identify(id, {
...(!!options && options),
});
}
};
type LogRocket = {
LogRocket: any;
initialized: boolean;
setEnabled: (enabled: boolean) => void;
};
export const LogRocketContext = React.createContext<LogRocket>({
LogRocket,
initialized: false,
setEnabled: (enabled: boolean) => {},
});
export const LogRocketProvider: React.FunctionComponent = ({
children,
}: React.PropsWithChildren) => {
const [initialized, setInitialized] = React.useState(false);
const [enabled, setEnabled] = React.useState(true);
if (typeof window !== "undefined") {
LogRocket.init("qfpqoi/radio-otherway", {
console: {
isEnabled: false,
},
});
}
const identifyViewer = React.useCallback(
(viewer: Viewer) => {
if (!viewer) return;
if (!initialized) {
initialize();
}
if (enabled && initialized) {
const { contact_id, ...rest } = viewer;
logRocketIdentify(contact_id, {
...rest,
});
setIdentified(viewer);
}
},
[enabled, initialized, initialize]
);
React.useEffect(() => {
identifyViewer(viewer);
}, [identified, initialized, viewer]);
React.useEffect(() => {
if (!initialized) {
initialize();
}
}, [initialized, initialize]);
return (
<LogRocketContext.Provider
value={{
LogRocket,
identified,
identifyViewer,
initialized,
setEnabled,
}}
>
{children}
</LogRocketContext.Provider>
);
};
export default function useLogRocket({ viewer }: { viewer: any }) {
const logRocketContext = React.useContext(LogRocketContext);
if (viewer) {
logRocketContext.identifyViewer(viewer);
}
return logRocketContext;
}

View File

@@ -1,7 +1,7 @@
import "firebase/messaging";
import localforage from "localforage";
import { getMessaging, getToken } from "firebase/messaging";
import { app } from "./firebase";
import { app } from "../../auth/firebase";
const firebaseCloudMessaging = {

View File

@@ -26,7 +26,10 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const user = u.data();
if (user.mobileNumber) {
console.log("notify", "sending notification to ", user);
const message = `New show from ${show.creator} starting now!!\nhttps://mixcloud.com/live/radiootherway`;
const message = (process.env.WHATSAPP_SHOW_HOUR as string)
.replace("{{1}}", user.displayName as string)
.replace("{{2}}", show.creator);
await sendWhatsApp(user.mobileNumber, message);
}
});

View File

@@ -1,14 +1,12 @@
import { NextApiRequest, NextApiResponse } from "next";
import db, { shows } from "@/lib/db";
import { getDocs, query, where } from "@firebase/firestore";
import { StatusCodes } from "http-status-codes";
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const q = query(
shows,
where("date", ">", new Date())
);
const q = query(shows, where("date", ">", new Date()));
const upcoming = await getDocs(q);
res.status(StatusCodes.OK).json(upcoming.docs.map(r => r.data()));
res.status(StatusCodes.OK).json(upcoming.docs.map((r) => r.data()));
res.end();
};
export default handler;

726
yarn.lock

File diff suppressed because it is too large Load Diff