mirror of
https://github.com/fergalmoran/radio-otherway.git
synced 2025-12-22 09:50:29 +00:00
Added logging & profile image
This commit is contained in:
15
.firebase/hosting.cHVibGlj.cache
Normal file
15
.firebase/hosting.cHVibGlj.cache
Normal 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
|
||||||
20
.github/workflows/firebase-hosting-merge.yml
vendored
Normal file
20
.github/workflows/firebase-hosting-merge.yml
vendored
Normal 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
|
||||||
17
.github/workflows/firebase-hosting-pull-request.yml
vendored
Normal file
17
.github/workflows/firebase-hosting-pull-request.yml
vendored
Normal 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
|
||||||
@@ -2,5 +2,19 @@
|
|||||||
"firestore": {
|
"firestore": {
|
||||||
"rules": "firestore.rules",
|
"rules": "firestore.rules",
|
||||||
"indexes": "firestore.indexes.json"
|
"indexes": "firestore.indexes.json"
|
||||||
|
},
|
||||||
|
"hosting": {
|
||||||
|
"public": "public",
|
||||||
|
"ignore": [
|
||||||
|
"firebase.json",
|
||||||
|
"**/.*",
|
||||||
|
"**/node_modules/**"
|
||||||
|
],
|
||||||
|
"rewrites": [
|
||||||
|
{
|
||||||
|
"source": "**",
|
||||||
|
"destination": "/index.html"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ const nextConfig = {
|
|||||||
experimental: {
|
experimental: {
|
||||||
appDir: true,
|
appDir: true,
|
||||||
},
|
},
|
||||||
}
|
images: {
|
||||||
|
domains: ["firebasestorage.googleapis.com"],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = nextConfig
|
module.exports = nextConfig;
|
||||||
|
|||||||
26
package.json
26
package.json
@@ -3,8 +3,8 @@
|
|||||||
"version": "0.1.1",
|
"version": "0.1.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"turbo": "NODE_OPTIONS='-r next-logger' next dev --turbo",
|
"turbo": "next dev --turbo",
|
||||||
"dev": "NODE_OPTIONS='-r next-logger' node ./server.js",
|
"dev": "node ./server.js",
|
||||||
"debug": "node ./server.js",
|
"debug": "node ./server.js",
|
||||||
"dev-nossl": "next dev",
|
"dev-nossl": "next dev",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
@@ -12,11 +12,13 @@
|
|||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@babel/plugin-transform-react-display-name": "^7.18.6",
|
||||||
"@headlessui/react": "^1.7.11",
|
"@headlessui/react": "^1.7.11",
|
||||||
"@next/font": "13.2.0",
|
"@next/font": "13.2.0",
|
||||||
"@prisma/client": "^4.9.0",
|
"@prisma/client": "^4.9.0",
|
||||||
"@types/feather-icons": "^4.29.1",
|
"@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": "18.0.28",
|
||||||
"@types/react-dom": "18.0.11",
|
"@types/react-dom": "18.0.11",
|
||||||
"@upstash/qstash": "^0.3.6",
|
"@upstash/qstash": "^0.3.6",
|
||||||
@@ -24,8 +26,8 @@
|
|||||||
"classnames": "^2.3.2",
|
"classnames": "^2.3.2",
|
||||||
"daisyui": "^2.49.0",
|
"daisyui": "^2.49.0",
|
||||||
"encoding": "^0.1.13",
|
"encoding": "^0.1.13",
|
||||||
"eslint": "8.32.0",
|
"eslint": "8.35.0",
|
||||||
"eslint-config-next": "^13.2.2",
|
"eslint-config-next": "13.2.3",
|
||||||
"feather-icons": "^4.29.0",
|
"feather-icons": "^4.29.0",
|
||||||
"firebase": "^9.17.1",
|
"firebase": "^9.17.1",
|
||||||
"firebase-admin": "^11.5.0",
|
"firebase-admin": "^11.5.0",
|
||||||
@@ -33,15 +35,16 @@
|
|||||||
"fireschema": "^4.0.4",
|
"fireschema": "^4.0.4",
|
||||||
"http-status-codes": "^2.2.0",
|
"http-status-codes": "^2.2.0",
|
||||||
"localforage": "^1.10.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-logger": "^3.0.1",
|
||||||
"next-seo": "^5.15.0",
|
"next-seo": "^5.15.0",
|
||||||
"pino": "^8.11.0",
|
"path-browserify": "^1.0.1",
|
||||||
"pino-logflare": "^0.3.12",
|
"react": "18.2.0",
|
||||||
"react": "^18.2.0",
|
|
||||||
"react-daisyui": "^3.0.3",
|
"react-daisyui": "^3.0.3",
|
||||||
"react-device-detect": "^2.2.3",
|
"react-device-detect": "^2.2.3",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-dropzone": "^14.2.3",
|
"react-dropzone": "^14.2.3",
|
||||||
"react-feather": "^2.0.10",
|
"react-feather": "^2.0.10",
|
||||||
"react-hook-form": "^7.43.2",
|
"react-hook-form": "^7.43.2",
|
||||||
@@ -50,12 +53,13 @@
|
|||||||
"react-phone-number-input": "^3.2.19",
|
"react-phone-number-input": "^3.2.19",
|
||||||
"theme-change": "^2.3.0",
|
"theme-change": "^2.3.0",
|
||||||
"twilio": "^4.8.0",
|
"twilio": "^4.8.0",
|
||||||
"typescript": "4.9.4",
|
"typescript": "4.9.5",
|
||||||
"zod": "^3.20.6"
|
"zod": "^3.20.6"
|
||||||
},
|
},
|
||||||
"packageManager": "yarn@1.22.19",
|
"packageManager": "yarn@1.22.19",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@google-cloud/local-auth": "2.1.1",
|
"@google-cloud/local-auth": "2.1.1",
|
||||||
|
"@hookform/devtools": "^4.3.0",
|
||||||
"autoprefixer": "^10.4.13",
|
"autoprefixer": "^10.4.13",
|
||||||
"googleapis": "111.0.0",
|
"googleapis": "111.0.0",
|
||||||
"postcss": "^8.4.21",
|
"postcss": "^8.4.21",
|
||||||
|
|||||||
89
public/index.html
Normal file
89
public/index.html
Normal 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…</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>
|
||||||
@@ -8,6 +8,12 @@ import { AuthUserProvider } from "@/lib/auth/authUserContext";
|
|||||||
import { themeChange } from "theme-change";
|
import { themeChange } from "theme-change";
|
||||||
import Script from "next/script";
|
import Script from "next/script";
|
||||||
import logger from "@/lib/util/logging";
|
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({
|
const font = Raleway({
|
||||||
weight: ["400", "700"],
|
weight: ["400", "700"],
|
||||||
@@ -24,20 +30,25 @@ const RootLayout = ({ children }: React.PropsWithChildren) => {
|
|||||||
return (
|
return (
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<Script src="/theme.js" strategy="beforeInteractive" />
|
<Script src="/theme.js" />
|
||||||
</head>
|
</head>
|
||||||
<body className={`${font.className}`}>
|
<LoggingProvider>
|
||||||
<AuthUserProvider>
|
<body className={`${font.className}`}>
|
||||||
<PushNotificationWrapper>
|
<ThemeProvider>
|
||||||
<div className="flex flex-col min-h-screen bg-base-100">
|
<Toaster />
|
||||||
<NavBar />
|
<AuthUserProvider>
|
||||||
<div className="items-end grow place-items-center bg-base-200 text-primary-content">
|
<PushNotificationWrapper>
|
||||||
<main className=" text-base-content">{children}</main>
|
<div className="flex flex-col min-h-screen bg-base-100">
|
||||||
</div>
|
<NavBar />
|
||||||
</div>
|
<div className="items-end grow place-items-center bg-base-200 text-base-content">
|
||||||
</PushNotificationWrapper>
|
<main className=" text-base-content">{children}</main>
|
||||||
</AuthUserProvider>
|
</div>
|
||||||
</body>
|
</div>
|
||||||
|
</PushNotificationWrapper>
|
||||||
|
</AuthUserProvider>
|
||||||
|
</ThemeProvider>
|
||||||
|
</body>
|
||||||
|
</LoggingProvider>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,33 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
const LogRocket = require("logrocket");
|
||||||
|
const setupLogRocketReact = require("logrocket-react");
|
||||||
|
import packageJson from "../../package.json";
|
||||||
|
|
||||||
const Loading = () => {
|
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 (
|
return (
|
||||||
<div role="status">
|
<div role="status">
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import HomePageComponent from "@/components/pages/home";
|
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 getData = async () => {
|
||||||
const res = await fetch(
|
const res = await fetch(
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { firebaseCloudMessaging } from "@/lib/auth/firebaseMessaging";
|
|
||||||
import * as firebase from "firebase/app";
|
import * as firebase from "firebase/app";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
@@ -11,87 +10,94 @@ import { useAuthUserContext } from "@/lib/auth/authUserContext";
|
|||||||
import { users } from "@/lib/db";
|
import { users } from "@/lib/db";
|
||||||
import logger from "@/lib/util/logging";
|
import logger from "@/lib/util/logging";
|
||||||
import { parseUserAgent } from "react-device-detect";
|
import { parseUserAgent } from "react-device-detect";
|
||||||
|
import { firebaseCloudMessaging } from "@/lib/util/notifications/firebaseMessaging";
|
||||||
|
|
||||||
const PushNotificationWrapper = ({ children }: React.PropsWithChildren) => {
|
const PushNotificationWrapper = ({ children }: React.PropsWithChildren) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { profile } = useAuthUserContext();
|
const { profile } = useAuthUserContext();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const _getAndStoreRegistrationToken = async () => {
|
const _getAndStoreRegistrationToken = async () => {
|
||||||
const { ua } = parseUserAgent(window.navigator.userAgent);
|
const { ua } = parseUserAgent(window.navigator.userAgent);
|
||||||
|
|
||||||
if (!profile) return;
|
if (!profile) return;
|
||||||
await setToken();
|
await setToken();
|
||||||
|
|
||||||
// Event listener that listens for the push notification event in the background
|
// Event listener that listens for the push notification event in the background
|
||||||
if ("serviceWorker" in navigator) {
|
if ("serviceWorker" in navigator) {
|
||||||
navigator.serviceWorker.addEventListener("message", (event) => {
|
navigator.serviceWorker.addEventListener("message", (event) => {
|
||||||
console.log("event for the service worker", 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() {
|
// Calls the getMessage() function if the token is there
|
||||||
const messaging = getMessaging(app);
|
async function setToken() {
|
||||||
onMessage(messaging, (message) => {
|
try {
|
||||||
ToastService.custom(
|
if (!profile) return;
|
||||||
<div
|
const token = await firebaseCloudMessaging.init();
|
||||||
onClick={() =>
|
if (token && profile) {
|
||||||
message?.data?.url &&
|
const newRegistration = {
|
||||||
handleClickPushNotification(message?.data?.url)
|
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>
|
const profileWithRegistrations = Object.assign({}, profile);
|
||||||
<h6>{message?.notification?.body}</h6>
|
await setDoc(doc(users, profile?.id), profileWithRegistrations, {
|
||||||
</div>
|
merge: true,
|
||||||
);
|
});
|
||||||
});
|
getMessage();
|
||||||
}
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
const handleClickPushNotification = (url: string) => {
|
}
|
||||||
router.push(url);
|
}
|
||||||
};
|
};
|
||||||
return (
|
_getAndStoreRegistrationToken().catch((err) => {
|
||||||
<>
|
logger.error(
|
||||||
<Toaster />
|
"PushNotificationWrapper",
|
||||||
{children}
|
"_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;
|
export default PushNotificationWrapper;
|
||||||
|
|||||||
@@ -1,19 +1,23 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import useFirebaseAuth from "@/lib/auth/useFirebaseAuth";
|
|
||||||
import { IoLogoFacebook, IoLogoGoogle, IoLogoTwitter } from "react-icons/io";
|
import { IoLogoFacebook, IoLogoGoogle, IoLogoTwitter } from "react-icons/io";
|
||||||
|
import { useFirebaseAuth } from "@/lib/auth";
|
||||||
|
|
||||||
const LoginPage = () => {
|
const LoginPage = () => {
|
||||||
const { signInWithGoogle, signInWithFacebook, signInWithTwitter, profile, signIn } =
|
const {
|
||||||
useFirebaseAuth();
|
signInWithGoogle,
|
||||||
|
signInWithFacebook,
|
||||||
|
signInWithTwitter,
|
||||||
|
profile,
|
||||||
|
signIn,
|
||||||
|
} = useFirebaseAuth();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [error, setError] = React.useState("");
|
const [error, setError] = React.useState("");
|
||||||
const [forgot, setForgot] = React.useState(false);
|
const [forgot, setForgot] = React.useState(false);
|
||||||
const [email, setEmail] = React.useState("");
|
const [email, setEmail] = React.useState("");
|
||||||
const [password, setPassword] = React.useState("");
|
const [password, setPassword] = React.useState("");
|
||||||
|
|
||||||
|
|
||||||
const login = async (
|
const login = async (
|
||||||
event: React.SyntheticEvent<HTMLButtonElement>
|
event: React.SyntheticEvent<HTMLButtonElement>
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
@@ -21,9 +25,7 @@ const LoginPage = () => {
|
|||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<div className="max-w-lg p-10 rounded-md shadow-md font-body bg-base-100 text-base-content md:flex-1">
|
<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">
|
<h3 className="my-4 text-2xl font-semibold font-title">Account Login</h3>
|
||||||
Account Login
|
|
||||||
</h3>
|
|
||||||
<form action="#" className="flex flex-col space-y-5">
|
<form action="#" className="flex flex-col space-y-5">
|
||||||
<div className="flex flex-col space-y-1">
|
<div className="flex flex-col space-y-1">
|
||||||
<label htmlFor="email" className="text-sm">
|
<label htmlFor="email" className="text-sm">
|
||||||
@@ -122,10 +124,18 @@ const LoginPage = () => {
|
|||||||
{error && (
|
{error && (
|
||||||
<div className="shadow-lg alert alert-error">
|
<div className="shadow-lg alert alert-error">
|
||||||
<div>
|
<div>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" className="flex-shrink-0 w-6 h-6 stroke-current" fill="none"
|
<svg
|
||||||
viewBox="0 0 24 24">
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2"
|
className="flex-shrink-0 w-6 h-6 stroke-current"
|
||||||
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
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>
|
</svg>
|
||||||
<span>{error}</span>
|
<span>{error}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import useFirebaseAuth from "@/lib/auth/useFirebaseAuth";
|
|
||||||
import { IoLogoFacebook, IoLogoGoogle, IoLogoTwitter } from "react-icons/io";
|
import { IoLogoFacebook, IoLogoGoogle, IoLogoTwitter } from "react-icons/io";
|
||||||
import { AiOutlineExclamationCircle } from "react-icons/ai";
|
import { AiOutlineExclamationCircle } from "react-icons/ai";
|
||||||
|
|
||||||
import { Info } from "react-feather";
|
import { Info } from "react-feather";
|
||||||
|
import { useFirebaseAuth } from "@/lib/auth";
|
||||||
|
|
||||||
const SignupPage = () => {
|
const SignupPage = () => {
|
||||||
const {
|
const {
|
||||||
|
|||||||
@@ -57,14 +57,6 @@ const ProfilePageComponent = () => {
|
|||||||
if (profile) {
|
if (profile) {
|
||||||
reset(profile);
|
reset(profile);
|
||||||
}
|
}
|
||||||
// setValue([
|
|
||||||
// { displayName: profile?.displayName },
|
|
||||||
// { email: profile?.email },
|
|
||||||
// { about: profile?.about },
|
|
||||||
// { photoURL: profile?.photoURL },
|
|
||||||
// { headerPhotoURL: profile?.headerPhotoURL },
|
|
||||||
// { mobileNumber: profile?.mobileNumber },
|
|
||||||
// ]);
|
|
||||||
}, [profile, reset]);
|
}, [profile, reset]);
|
||||||
const onSubmit: SubmitHandler<ProfileForm> = async (data) => {
|
const onSubmit: SubmitHandler<ProfileForm> = async (data) => {
|
||||||
console.log(data);
|
console.log(data);
|
||||||
@@ -93,7 +85,7 @@ const ProfilePageComponent = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
React.useEffect(() => {}, [selectedItem]);
|
useEffect(() => {}, [selectedItem]);
|
||||||
const _getView = () => {
|
const _getView = () => {
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <div>Loading</div>;
|
return <div>Loading</div>;
|
||||||
@@ -140,6 +132,7 @@ const ProfilePageComponent = () => {
|
|||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
{selectedItem === "profile" ? (
|
{selectedItem === "profile" ? (
|
||||||
<ProfilePageComponentProfile
|
<ProfilePageComponentProfile
|
||||||
|
setValue={setValue}
|
||||||
register={register}
|
register={register}
|
||||||
profile={profile}
|
profile={profile}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,20 +1,23 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { HeadingSubComponent } from "@/components/widgets/text";
|
import { HeadingSubComponent } from "@/components/widgets/text";
|
||||||
import React from "react";
|
import React, { useState } from "react";
|
||||||
import { UseFormRegister } from "react-hook-form";
|
import { UseFormRegister, UseFormSetValue } from "react-hook-form";
|
||||||
import { ProfileForm } from "@/components/pages/profile/ProfilePageComponent";
|
import { ProfileForm } from "@/components/pages/profile/ProfilePageComponent";
|
||||||
import { Profile } from "@/models";
|
import { Profile } from "@/models";
|
||||||
import InputText from "@/components/widgets/inputs/InputText";
|
import { FirebaseImageUpload, InputText } from "@/components/widgets/inputs";
|
||||||
import { InputTextArea } from "@/components/widgets/inputs";
|
import { InputTextArea } from "@/components/widgets/inputs";
|
||||||
|
|
||||||
interface IProfilePageComponentProfileProps {
|
interface IProfilePageComponentProfileProps {
|
||||||
register: UseFormRegister<ProfileForm>;
|
|
||||||
profile: Profile;
|
profile: Profile;
|
||||||
|
register: UseFormRegister<ProfileForm>;
|
||||||
|
setValue: UseFormSetValue<ProfileForm>;
|
||||||
}
|
}
|
||||||
const ProfilePageComponentProfile = ({
|
const ProfilePageComponentProfile = ({
|
||||||
register,
|
|
||||||
profile,
|
profile,
|
||||||
|
register,
|
||||||
|
setValue,
|
||||||
}: IProfilePageComponentProfileProps) => {
|
}: IProfilePageComponentProfileProps) => {
|
||||||
|
const [photoURLFile, setPhotoURLFile] = useState("");
|
||||||
return (
|
return (
|
||||||
<div className="space-y-8 divide-y sm:space-y-5">
|
<div className="space-y-8 divide-y sm:space-y-5">
|
||||||
<div>
|
<div>
|
||||||
@@ -83,23 +86,15 @@ const ProfilePageComponentProfile = ({
|
|||||||
subHeading="Upload a picture to distinguish you from the rest"
|
subHeading="Upload a picture to distinguish you from the rest"
|
||||||
/>
|
/>
|
||||||
<div className="mt-1 sm:col-span-2 sm:mt-0">
|
<div className="mt-1 sm:col-span-2 sm:mt-0">
|
||||||
<div className="flex items-center">
|
<FirebaseImageUpload
|
||||||
<span className="w-12 h-12 overflow-hidden rounded-full">
|
forType="user"
|
||||||
<svg
|
imageType="avatar"
|
||||||
className="w-full h-full "
|
itemId={profile.id}
|
||||||
fill="currentColor"
|
imageUrl={profile.photoURL}
|
||||||
viewBox="0 0 24 24"
|
controlName="photoURL"
|
||||||
>
|
setValue={setValue}
|
||||||
<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" />
|
{...register("photoURL")}
|
||||||
</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>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -109,40 +104,7 @@ const ProfilePageComponentProfile = ({
|
|||||||
subHeading="Upload a wide photo for the top of your profile page"
|
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="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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
7
src/components/providers/LoggingProvider.tsx
Normal file
7
src/components/providers/LoggingProvider.tsx
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const LoggingProvider = ({ children }: React.PropsWithChildren) => {
|
||||||
|
return <>{children}</>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LoggingProvider;
|
||||||
14
src/components/providers/ThemeProvider.tsx
Normal file
14
src/components/providers/ThemeProvider.tsx
Normal 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;
|
||||||
3
src/components/providers/index.ts
Normal file
3
src/components/providers/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import LoggingProvider from "./LoggingProvider";
|
||||||
|
import ThemeProvider from "./ThemeProvider";
|
||||||
|
export { LoggingProvider, ThemeProvider };
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import useFirebaseAuth from "@/lib/auth/useFirebaseAuth";
|
import { useFirebaseAuth } from "@/lib/auth";
|
||||||
import { Show } from "@/models";
|
import { Show } from "@/models";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { MdAddAlarm } from "react-icons/md";
|
import { MdAddAlarm } from "react-icons/md";
|
||||||
|
|||||||
199
src/components/widgets/inputs/FirebaseImageUploader.tsx
Normal file
199
src/components/widgets/inputs/FirebaseImageUploader.tsx
Normal 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;
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @next/next/no-img-element */
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import Dropzone, { DropzoneRef, useDropzone } from "react-dropzone";
|
import Dropzone, { DropzoneRef, useDropzone } from "react-dropzone";
|
||||||
|
|
||||||
@@ -25,6 +26,7 @@ const ImageUpload = () => {
|
|||||||
<div id="preview">
|
<div id="preview">
|
||||||
{acceptedFiles[0] && (
|
{acceptedFiles[0] && (
|
||||||
<img
|
<img
|
||||||
|
alt="Uploaded image"
|
||||||
className="object-cover"
|
className="object-cover"
|
||||||
src={URL.createObjectURL(acceptedFiles[0])}
|
src={URL.createObjectURL(acceptedFiles[0])}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ import React, { forwardRef } from "react";
|
|||||||
import ITextInputProps from "./props";
|
import ITextInputProps from "./props";
|
||||||
|
|
||||||
const InputText = forwardRef<HTMLInputElement, ITextInputProps>(
|
const InputText = forwardRef<HTMLInputElement, ITextInputProps>(
|
||||||
({ id, type, placeholder, label, showLabel = true, onChange, onBlur }, ref) => {
|
(
|
||||||
|
{ id, type, placeholder, label, showLabel = true, onChange, onBlur },
|
||||||
|
ref
|
||||||
|
) => {
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{showLabel && (
|
{showLabel && (
|
||||||
@@ -26,5 +28,3 @@ const InputText = forwardRef<HTMLInputElement, ITextInputProps>(
|
|||||||
);
|
);
|
||||||
InputText.displayName = "InputTextAreaComponent";
|
InputText.displayName = "InputTextAreaComponent";
|
||||||
export default InputText;
|
export default InputText;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import ImageUpload from "./ImageUpload";
|
import ImageUpload from "./ImageUpload";
|
||||||
import InputText from "./InputText";
|
import InputText from "./InputText";
|
||||||
import InputTextArea from "./InputTextArea";
|
import InputTextArea from "./InputTextArea";
|
||||||
|
import FirebaseImageUpload from "./FirebaseImageUploader";
|
||||||
|
|
||||||
export { ImageUpload };
|
export { InputText, ImageUpload, InputTextArea, FirebaseImageUpload };
|
||||||
export { InputTextArea };
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { firebaseCloudMessaging } from "@/lib/auth/firebaseMessaging";
|
|
||||||
import localforage from "localforage";
|
import localforage from "localforage";
|
||||||
|
import { firebaseCloudMessaging } from "@/lib/util/notifications/firebaseMessaging";
|
||||||
|
|
||||||
const RequestPushNotifications = () => {
|
const RequestPushNotifications = () => {
|
||||||
const _checkNotifications = async () => {
|
const _checkNotifications = async () => {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import InputText from "../inputs/InputText";
|
|
||||||
import { UseFormRegister } from "react-hook-form";
|
import { UseFormRegister } from "react-hook-form";
|
||||||
import { ProfileForm } from "@/components/pages/profile/ProfilePageComponent";
|
import { ProfileForm } from "@/components/pages/profile/ProfilePageComponent";
|
||||||
import { Profile } from "@/models";
|
import { Profile } from "@/models";
|
||||||
|
|||||||
@@ -1,24 +1,25 @@
|
|||||||
import React, { createContext, useContext, Context } from "react";
|
import React, { createContext, useContext, Context } from "react";
|
||||||
import useFirebaseAuth from "@/lib/auth/useFirebaseAuth";
|
|
||||||
import { Profile } from "@/models";
|
import { Profile } from "@/models";
|
||||||
|
import useFirebaseAuth from "./useFirebaseAuth";
|
||||||
|
|
||||||
interface IAuthUserContext {
|
interface IAuthUserContext {
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
profile: Profile | undefined,
|
profile: Profile | undefined;
|
||||||
logOut: () => void
|
logOut: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const authUserContext = createContext<IAuthUserContext>({
|
const authUserContext = createContext<IAuthUserContext>({
|
||||||
loading: true,
|
loading: true,
|
||||||
profile: undefined,
|
profile: undefined,
|
||||||
logOut: () => {
|
logOut: () => {},
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export function AuthUserProvider({ children }: { children: React.ReactNode }) {
|
export function AuthUserProvider({ children }: { children: React.ReactNode }) {
|
||||||
const { loading, profile, logOut } = useFirebaseAuth();
|
const { loading, profile, logOut } = useFirebaseAuth();
|
||||||
return (
|
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
3
src/lib/auth/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import useFirebaseAuth from "./useFirebaseAuth";
|
||||||
|
|
||||||
|
export { useFirebaseAuth };
|
||||||
@@ -44,8 +44,8 @@ export default function useFirebaseAuth() {
|
|||||||
const profile: Profile = new Profile(
|
const profile: Profile = new Profile(
|
||||||
auth.currentUser.uid,
|
auth.currentUser.uid,
|
||||||
(savedProfile?.email || auth.currentUser.email) as string,
|
(savedProfile?.email || auth.currentUser.email) as string,
|
||||||
(savedProfile?.displayName || auth.currentUser.email) as string,
|
(savedProfile?.displayName || auth.currentUser.displayName) as string,
|
||||||
(savedProfile?.photoURL || auth.currentUser.email) as string,
|
(savedProfile?.photoURL || auth.currentUser.photoURL) as string,
|
||||||
savedProfile?.about as string,
|
savedProfile?.about as string,
|
||||||
savedProfile?.mobileNumber as string,
|
savedProfile?.mobileNumber as string,
|
||||||
new Date(),
|
new Date(),
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import firebase, { getApp, getApps, initializeApp } from "firebase/app";
|
import firebase, { getApp, getApps, initializeApp } from "firebase/app";
|
||||||
|
import { getStorage } from "firebase/storage";
|
||||||
import "firebase/auth";
|
import "firebase/auth";
|
||||||
// import { getFirestore } from "firebase/firestore";
|
|
||||||
|
|
||||||
|
|
||||||
const firebaseConfig = {
|
const firebaseConfig = {
|
||||||
apiKey: "AIzaSyDtk_Ym-AZroXsHvQVcdHXYyc_TvgycAWw",
|
apiKey: "AIzaSyDtk_Ym-AZroXsHvQVcdHXYyc_TvgycAWw",
|
||||||
@@ -10,8 +9,9 @@ const firebaseConfig = {
|
|||||||
storageBucket: "radio-otherway.appspot.com",
|
storageBucket: "radio-otherway.appspot.com",
|
||||||
messagingSenderId: "47147490249",
|
messagingSenderId: "47147490249",
|
||||||
appId: "1:47147490249:web:a84515b3ce1c481826e618",
|
appId: "1:47147490249:web:a84515b3ce1c481826e618",
|
||||||
measurementId: "G-12YB78EZM4"
|
measurementId: "G-12YB78EZM4",
|
||||||
};
|
};
|
||||||
|
|
||||||
// export default admin.firestore();
|
// export default admin.firestore();
|
||||||
export const app = initializeApp(firebaseConfig);
|
export const app = initializeApp(firebaseConfig);
|
||||||
|
export const storage = getStorage(app);
|
||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
QueryDocumentSnapshot,
|
QueryDocumentSnapshot,
|
||||||
SnapshotOptions,
|
SnapshotOptions,
|
||||||
Timestamp,
|
Timestamp,
|
||||||
|
initializeFirestore,
|
||||||
} from "firebase/firestore";
|
} from "firebase/firestore";
|
||||||
|
|
||||||
const firebaseConfig = {
|
const firebaseConfig = {
|
||||||
@@ -20,6 +21,9 @@ const firebaseConfig = {
|
|||||||
measurementId: "G-12YB78EZM4",
|
measurementId: "G-12YB78EZM4",
|
||||||
};
|
};
|
||||||
export const firebaseApp = initializeApp(firebaseConfig);
|
export const firebaseApp = initializeApp(firebaseConfig);
|
||||||
|
initializeFirestore(firebaseApp, {
|
||||||
|
ignoreUndefinedProperties: true,
|
||||||
|
});
|
||||||
const firestore = getFirestore();
|
const firestore = getFirestore();
|
||||||
const createCollection = <T = DocumentData>(collectionName: string) => {
|
const createCollection = <T = DocumentData>(collectionName: string) => {
|
||||||
return collection(firestore, collectionName) as CollectionReference<T>;
|
return collection(firestore, collectionName) as CollectionReference<T>;
|
||||||
@@ -64,6 +68,7 @@ import {
|
|||||||
Profile,
|
Profile,
|
||||||
NotificationSchedule,
|
NotificationSchedule,
|
||||||
} from "@/models";
|
} from "@/models";
|
||||||
|
import { storage } from "./firebaseConfig";
|
||||||
// export all your collections
|
// export all your collections
|
||||||
|
|
||||||
export const users = createCollection<Profile>("users");
|
export const users = createCollection<Profile>("users");
|
||||||
@@ -77,6 +82,5 @@ export const notificationSchedules =
|
|||||||
export const reminders = createCollection<Reminder>("reminders");
|
export const reminders = createCollection<Reminder>("reminders");
|
||||||
export const remindersProcessed =
|
export const remindersProcessed =
|
||||||
createCollection<RemindersProcessed>("reminders");
|
createCollection<RemindersProcessed>("reminders");
|
||||||
|
|
||||||
export default firestore;
|
export default firestore;
|
||||||
export { createCollection };
|
export { createCollection, firebaseConfig, storage };
|
||||||
|
|||||||
2
src/lib/util/fileUtils.ts
Normal file
2
src/lib/util/fileUtils.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
const getFileExtension = (file: File) => file.name.split(".").pop();
|
||||||
|
export { getFileExtension };
|
||||||
89
src/lib/util/logging/useLogRocket.tsx
Normal file
89
src/lib/util/logging/useLogRocket.tsx
Normal 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;
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import "firebase/messaging";
|
import "firebase/messaging";
|
||||||
import localforage from "localforage";
|
import localforage from "localforage";
|
||||||
import { getMessaging, getToken } from "firebase/messaging";
|
import { getMessaging, getToken } from "firebase/messaging";
|
||||||
import { app } from "./firebase";
|
import { app } from "../../auth/firebase";
|
||||||
|
|
||||||
|
|
||||||
const firebaseCloudMessaging = {
|
const firebaseCloudMessaging = {
|
||||||
@@ -26,7 +26,10 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||||||
const user = u.data();
|
const user = u.data();
|
||||||
if (user.mobileNumber) {
|
if (user.mobileNumber) {
|
||||||
console.log("notify", "sending notification to ", user);
|
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);
|
await sendWhatsApp(user.mobileNumber, message);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
import { NextApiRequest, NextApiResponse } from "next";
|
import { NextApiRequest, NextApiResponse } from "next";
|
||||||
import db, { shows } from "@/lib/db";
|
import db, { shows } from "@/lib/db";
|
||||||
import { getDocs, query, where } from "@firebase/firestore";
|
import { getDocs, query, where } from "@firebase/firestore";
|
||||||
|
import { StatusCodes } from "http-status-codes";
|
||||||
|
|
||||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
const q = query(
|
const q = query(shows, where("date", ">", new Date()));
|
||||||
shows,
|
|
||||||
where("date", ">", new Date())
|
|
||||||
);
|
|
||||||
const upcoming = await getDocs(q);
|
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();
|
res.end();
|
||||||
};
|
};
|
||||||
export default handler;
|
export default handler;
|
||||||
|
|||||||
Reference in New Issue
Block a user