Add notification & scheduling callbacks

This commit is contained in:
Fergal Moran
2023-03-01 13:21:20 +00:00
parent bf4c349b7e
commit 48cacb3db4
43 changed files with 1038 additions and 1638 deletions

7
.vscode/launch.json vendored
View File

@@ -7,7 +7,12 @@
"name": "Launch Program", "name": "Launch Program",
"skipFiles": ["<node_internals>/**"], "skipFiles": ["<node_internals>/**"],
"program": "${workspaceFolder}/server.js", "program": "${workspaceFolder}/server.js",
"outFiles": ["${workspaceFolder}/**/*.js"] "outFiles": ["${workspaceFolder}/**/*.js"],
"outputCapture": "std",
"resolveSourceMapLocations": [
"${workspaceFolder}/**",
"!**/node_modules/**"
]
} }
] ]
} }

View File

@@ -6,9 +6,11 @@
"**/CVS": true, "**/CVS": true,
"**/.DS_Store": true, "**/.DS_Store": true,
"**/Thumbs.db": true, "**/Thumbs.db": true,
".vscode":true, ".vscode": true,
".next": true, ".next": true,
".vercel": true ".vercel": true,
".working": true,
".yarn": true
}, },
"typescript.tsdk": "node_modules/typescript/lib", "typescript.tsdk": "node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true "typescript.enablePromptUseWorkspaceTsdk": true

File diff suppressed because it is too large Load Diff

5
.yarnrc Normal file
View File

@@ -0,0 +1,5 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
yarn-path ".yarn/releases/yarn-1.22.19.cjs"

View File

@@ -20,30 +20,34 @@
"@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",
"axios": "^1.3.4",
"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.32.0",
"eslint-config-next": "13.1.5", "eslint-config-next": "^13.2.2",
"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",
"firebase-functions": "^4.2.1", "firebase-functions": "^4.2.1",
"fireschema": "^4.0.4", "fireschema": "^4.0.4",
"http-status-codes": "^2.2.0",
"localforage": "^1.10.0", "localforage": "^1.10.0",
"next": "^13.2.1", "next": "^13.2.2",
"next-logger": "^3.0.1", "next-logger": "^3.0.1",
"next-seo": "^5.15.0", "next-seo": "^5.15.0",
"pino": "^8.11.0", "pino": "^8.11.0",
"pino-logflare": "^0.3.12", "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-hot-toast": "^2.4.0", "react-hot-toast": "^2.4.0",
"react-icons": "^4.7.1", "react-icons": "^4.7.1",
"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.4",

4
public/theme.js Normal file
View File

@@ -0,0 +1,4 @@
(function initTheme() {
var theme = localStorage.getItem("theme") || "synthwave";
document.querySelector("html").setAttribute("data-theme", theme);
})();

13
src/app/(auth)/layout.tsx Normal file
View File

@@ -0,0 +1,13 @@
import React from "react";
const RootLayout = ({ children }: React.PropsWithChildren) => {
return (
<div className="flex flex-wrap w-full justify-evenly">
<div className="max-w-lg p-10 mt-6 rounded-md shadow-md font-body bg-base-100 text-base-content md:flex-1">
{children}
</div>
</div>
);
};
export default RootLayout;

View File

@@ -2,12 +2,7 @@ import { LoginPage } from "@/components/pages/auth";
import React from "react"; import React from "react";
const Login = async () => { const Login = async () => {
return ( return <LoginPage />;
<>
<div className="flex flex-wrap w-full justify-evenly">
<LoginPage />
</div>
</>);
}; };
export default Login; export default Login;

View File

@@ -2,7 +2,7 @@ import React from "react";
import { SignupPage } from "@/components/pages/auth"; import { SignupPage } from "@/components/pages/auth";
const Signup = async () => { const Signup = async () => {
return <div className="flex flex-wrap w-full justify-evenly"><SignupPage /></div>; return <SignupPage />;
}; };
export default Signup; export default Signup;

View File

@@ -1,7 +1,7 @@
import React from "react"; import React from "react";
import Script from "next/script";
export default function Head() { export default function Head() {
return ( return (
<> <>
<title>Radio::Otherway</title> <title>Radio::Otherway</title>
@@ -31,7 +31,6 @@ export default function Head() {
<link rel="manifest" href="/site.webmanifest" /> <link rel="manifest" href="/site.webmanifest" />
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5" /> <link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5" />
{/*need to include this here to avoid the FLUC on initial load*/} {/*need to include this here to avoid the FLUC on initial load*/}
<script src="https://unpkg.com/theme-change@2.2.0/index.js" />
<meta name="msapplication-TileColor" content="#da532c" /> <meta name="msapplication-TileColor" content="#da532c" />
<meta name="theme-color" content="#ffffff" /> <meta name="theme-color" content="#ffffff" />
</> </>

View File

@@ -1,3 +1,4 @@
/* eslint-disable @next/next/no-before-interactive-script-outside-document */
"use client"; "use client";
import React from "react"; import React from "react";
import "./globals.css"; import "./globals.css";
@@ -5,37 +6,39 @@ import { Raleway } from "@next/font/google";
import { NavBar, PushNotificationWrapper } from "@/components/layout"; import { NavBar, PushNotificationWrapper } from "@/components/layout";
import { AuthUserProvider } from "@/lib/auth/authUserContext"; import { AuthUserProvider } from "@/lib/auth/authUserContext";
import { themeChange } from "theme-change"; import { themeChange } from "theme-change";
import Script from "next/script";
import logger from "@/lib/util/logging";
const font = Raleway({ const font = Raleway({
weight: ["400", "700"], weight: ["400", "700"],
subsets: ["latin"], subsets: ["latin"],
variable: "--font-raleway" variable: "--font-raleway",
}); });
export default function RootLayout({ const RootLayout = ({ children }: React.PropsWithChildren) => {
children
}: {
children: React.ReactNode;
}) {
React.useEffect(() => { React.useEffect(() => {
logger.info("Bootstrapping application");
themeChange(false); themeChange(false);
}, []); }, []);
return ( return (
<html lang="en"> <html lang="en">
<head /> <head>
<body className={`${font.className}`}> <Script src="/theme.js" strategy="beforeInteractive" />
<AuthUserProvider> </head>
<PushNotificationWrapper> <body className={`${font.className}`}>
<div className="flex flex-col min-h-screen bg-base-100"> <AuthUserProvider>
<NavBar /> <PushNotificationWrapper>
<div className="items-end grow place-items-center bg-base-200 text-primary-content"> <div className="flex flex-col min-h-screen bg-base-100">
<main className=" text-base-content">{children}</main> <NavBar />
</div> <div className="items-end grow place-items-center bg-base-200 text-primary-content">
</div> <main className=" text-base-content">{children}</main>
</PushNotificationWrapper> </div>
</AuthUserProvider> </div>
</body> </PushNotificationWrapper>
</AuthUserProvider>
</body>
</html> </html>
); );
} };
export default RootLayout;

View File

@@ -3,24 +3,37 @@ import React from "react";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import useFirebaseAuth from "@/lib/auth/useFirebaseAuth"; 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 { Info } from "react-feather";
const SignupPage = () => { const SignupPage = () => {
const { signInWithGoogle, signInWithFacebook, signInWithTwitter, profile, signUp } = const {
useFirebaseAuth(); signInWithGoogle,
signInWithFacebook,
signInWithTwitter,
profile,
signUp,
linkAccounts
} = useFirebaseAuth();
const router = useRouter(); const router = useRouter();
const [error, setError] = React.useState(""); const [error, setError] = React.useState("");
const [email, setEmail] = React.useState(""); const [email, setEmail] = React.useState("");
const [password, setPassword] = React.useState(""); const [password, setPassword] = React.useState("");
const [confirmPassword, setConfirmPassword] = React.useState(""); const [confirmPassword, setConfirmPassword] = React.useState("");
const [linking, setLinking] = React.useState(false);
const register = async ( const register = async (
$event: React.SyntheticEvent<HTMLButtonElement> $event: React.SyntheticEvent<HTMLButtonElement>
): Promise<void> => { ): Promise<void> => {
$event.preventDefault(); $event.preventDefault();
setError("");
setLinking(false);
const result = await signUp(email, password); const result = await signUp(email, password);
if (result === "auth/email-already-in-use") { if (result === "auth/email-already-in-use") {
setError("This email address has already been used to create an account."); setLinking(true);
setError("");
// setError("This email address has already been used to create an account.");
} else if (result === "auth/invalid-email") { } else if (result === "auth/invalid-email") {
setError("Please enter a correct email address"); setError("Please enter a correct email address");
} else { } else {
@@ -33,12 +46,20 @@ const SignupPage = () => {
Create New Account Create New Account
</h3> </h3>
{error && ( {error && (
<div className="shadow-lg alert alert-error mb-4"> <div className="mb-4 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>
@@ -147,7 +168,6 @@ const SignupPage = () => {
</span> </span>
</button> </button>
</div> </div>
</div> </div>
</form> </form>
</div> </div>

View File

@@ -1,11 +1,26 @@
"use client"; "use client";
import React from "react"; import React, { useEffect, useMemo } from "react";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { User, Bell } from "react-feather"; import { User, Bell } from "react-feather";
import classNames from "classnames"; import classNames from "classnames";
import { useAuthUserContext } from "@/lib/auth/authUserContext"; import { useAuthUserContext } from "@/lib/auth/authUserContext";
import ProfilePageComponentProfile from "./ProfilePageComponentProfile"; import ProfilePageComponentProfile from "./ProfilePageComponentProfile";
import ProfilePageComponentNotifications from "./ProfilePageComponentNotifications"; import ProfilePageComponentNotifications from "./ProfilePageComponentNotifications";
import { SubmitHandler, useForm } from "react-hook-form";
import { users } from "@/lib/db";
import { doc, setDoc } from "firebase/firestore";
import ToastService from "@/components/widgets/toast";
import logger from "@/lib/util/logging";
import { removeUndefinedProperties } from "@/lib/util/objectUtils";
export type ProfileForm = {
displayName: string;
email: string;
about: string;
photoURL: string;
headerPhotoURL: string;
mobileNumber: string;
};
const ProfilePageComponent = () => { const ProfilePageComponent = () => {
const { profile, loading } = useAuthUserContext(); const { profile, loading } = useAuthUserContext();
@@ -15,7 +30,6 @@ const ProfilePageComponent = () => {
router.push("/"); router.push("/");
} }
}, [profile, loading, router]); }, [profile, loading, router]);
const [sendReminders, setSendReminders] = React.useState(false);
const subNavigation = [ const subNavigation = [
{ name: "profile", title: "Profile", icon: User }, { name: "profile", title: "Profile", icon: User },
{ {
@@ -25,12 +39,59 @@ const ProfilePageComponent = () => {
}, },
]; ];
const [selectedItem, setSelectedItem] = React.useState("profile"); const [selectedItem, setSelectedItem] = React.useState("profile");
const [userName, setUserName] = React.useState(""); const {
const [password, setPassword] = React.useState(""); register,
const [about, setAbout] = React.useState(""); handleSubmit,
const [displayName, setDisplayName] = React.useState(""); setValue,
const [url, setUrl] = React.useState(""); reset,
const [image, setImage] = React.useState(""); watch,
control,
formState: { errors },
} = useForm<ProfileForm>({
defaultValues: useMemo(() => {
return profile;
}, [profile]),
});
useEffect(() => {
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);
try {
const newProfile = removeUndefinedProperties({
displayName: data.displayName,
email: data.email,
about: data.about,
photoURL: data.photoURL,
headerPhotoURL: data.headerPhotoURL,
mobileNumber: data.mobileNumber,
lastSeen: new Date(),
});
const result = await setDoc(
doc(users, profile?.id),
Object.assign({}, newProfile),
{
merge: true,
}
);
console.log("ProfilePageComponentProfile", "_submitProfileForm", result);
ToastService.success("Successfully updated your profile", "Success");
} catch (err) {
logger.error("ProfilePageComponentProfile", "_submitProfileForm", err);
ToastService.error("Failed to update your profile.");
}
};
React.useEffect(() => {}, [selectedItem]); React.useEffect(() => {}, [selectedItem]);
const _getView = () => { const _getView = () => {
@@ -76,11 +137,30 @@ const ProfilePageComponent = () => {
</aside> </aside>
<div className="p-4 divide-y lg:col-span-9"> <div className="p-4 divide-y lg:col-span-9">
{selectedItem === "profile" ? ( <form onSubmit={handleSubmit(onSubmit)}>
<ProfilePageComponentProfile /> {selectedItem === "profile" ? (
) : ( <ProfilePageComponentProfile
<ProfilePageComponentNotifications /> register={register}
)} profile={profile}
/>
) : (
<ProfilePageComponentNotifications
register={register}
control={control}
profile={profile}
/>
)}
<div className="pt-5">
<div className="flex justify-end space-x-2">
<button type="button" className="btn-warning btn">
Cancel
</button>
<button type="submit" className="btn-success btn">
Save
</button>
</div>
</div>
</form>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -4,54 +4,66 @@ import {
PhoneNumber, PhoneNumber,
RequestPushNotifications, RequestPushNotifications,
} from "@/components/widgets/notifications"; } from "@/components/widgets/notifications";
import { UseFormRegister } from "react-hook-form";
import { ProfileForm } from "@/components/pages/profile/ProfilePageComponent";
import { Profile } from "@/models";
interface IProfilePageComponentNotificationsProps {
register: UseFormRegister<ProfileForm>;
control: any;
profile: Profile;
}
const ProfilePageComponentNotifications = ({
register,
control,
profile,
}: IProfilePageComponentNotificationsProps) => {
const [notificationPermissionsGranted, setNotificationPermissionsGranted] =
React.useState(Notification.permission !== "granted");
React.useEffect(() => {
setNotificationPermissionsGranted(Notification.permission !== "granted");
}, []);
const ProfilePageComponentNotifications = () => {
return ( return (
<form className="space-y-8 divide-y "> <div className="space-y-8 divide-y sm:space-y-5">
<div className="space-y-8 divide-y sm:space-y-5"> <div>
<div> <div>
<div> <h3 className="text-lg font-medium leading-6 ">Notifications</h3>
<h3 className="text-lg font-medium leading-6 ">Notifications</h3> <p className="max-w-2xl mt-1 text-sm ">
<p className="max-w-2xl mt-1 text-sm "> Here you can setup various different forms of notifications
Here you can setup various different forms of notifications </p>
</p> </div>
</div> <div className="mt-6 space-y-6 sm:mt-5 sm:space-y-5">
<div className="mt-6 space-y-6 sm:mt-5 sm:space-y-5"> <div className="space-x-3 sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-t sm:pt-5">
<div className="space-x-3 sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-t sm:pt-5"> <HeadingSubComponent
<HeadingSubComponent title="Browser Notifications"
title="Browser Notifications" subHeading="We'll send you an alert through (this) browser when a show is about to start."
subHeading="We'll send you an alert through (this) browser when a show is about to start." />
/> <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 max-w-lg rounded-md shadow-sm">
<div className="flex max-w-lg rounded-md shadow-sm"> {notificationPermissionsGranted ? (
<div>Permissions granted</div>
) : (
<RequestPushNotifications /> <RequestPushNotifications />
</div> )}
</div> </div>
</div> </div>
<div className="space-x-3 sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-t sm:pt-5"> </div>
<HeadingSubComponent <div className="space-x-3 sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-t sm:pt-5">
title="Phone number" <HeadingSubComponent
subHeading="We'll try to send you a WhatsApp message when a show is about to start" title="Phone number"
/> subHeading="We'll try to send you a WhatsApp message when a show is about to start"
<div className="mt-1 sm:col-span-2 sm:mt-0"> />
<div className="flex max-w-lg rounded-md shadow-sm"> <div className="mt-1 sm:col-span-2 sm:mt-0">
<PhoneNumber <div className="flex max-w-lg rounded-md shadow-sm">
phoneNumber="12354" <PhoneNumber name="mobileNumber" control={control} />
setPhoneNumber={(value) =>
console.log(
"ProfilePageComponentNotifications",
"setPhoneNumber",
value
)
}
/>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</form> </div>
); );
}; };

View File

@@ -1,205 +1,153 @@
"use client"; "use client";
import { InputText } from "@/components/widgets/inputs";
import { HeadingSubComponent } from "@/components/widgets/text"; import { HeadingSubComponent } from "@/components/widgets/text";
import { useAuthUserContext } from "@/lib/auth/authUserContext";
import db, { users } from "@/lib/db";
import { debug } from "console";
import { doc, setDoc } from "firebase/firestore";
import React from "react"; import React from "react";
import ToastService from "@/components/widgets/toast"; import { UseFormRegister } from "react-hook-form";
import logger from "@/lib/util/logging"; import { ProfileForm } from "@/components/pages/profile/ProfilePageComponent";
import { Profile } from "@/models";
import InputText from "@/components/widgets/inputs/InputText";
import { InputTextArea } from "@/components/widgets/inputs";
const ProfilePageComponentProfile = () => { interface IProfilePageComponentProfileProps {
const { loading, profile } = useAuthUserContext(); register: UseFormRegister<ProfileForm>;
const [sendReminders, setSendReminders] = React.useState(false); profile: Profile;
const [email, setEmail] = React.useState(""); }
const [password, setPassword] = React.useState(""); const ProfilePageComponentProfile = ({
const [about, setAbout] = React.useState(""); register,
const [displayName, setDisplayName] = React.useState(""); profile,
const [url, setUrl] = React.useState(""); }: IProfilePageComponentProfileProps) => {
const [image, setImage] = React.useState("");
React.useEffect(() => {
if (profile) {
setEmail(profile.email as string);
setDisplayName(profile.displayName as string);
setAbout(profile.about as string);
}
}, [profile]);
const _submitProfileForm = async ($event: React.SyntheticEvent) => {
$event.preventDefault();
try {
const result = await setDoc(
doc(users, profile?.id),
Object.assign(
{},
{
email,
displayName,
about: about || "",
lastSeen: new Date()
}
),
{ merge: true }
);
console.log("ProfilePageComponentProfile", "_submitProfileForm", result);
ToastService.success("Successfully updated your profile", "Success");
} catch (err) {
logger.error("ProfilePageComponentProfile", "_submitProfileForm", err);
ToastService.error("Failed to update your profile.");
}
};
return ( return (
<form className="space-y-8 divide-y" onSubmit={_submitProfileForm}> <div className="space-y-8 divide-y sm:space-y-5">
<div className="space-y-8 divide-y sm:space-y-5"> <div>
<div> <div>
<div> <h3 className="text-lg font-medium leading-6 ">Profile</h3>
<h3 className="text-lg font-medium leading-6 ">Profile</h3> <p className="max-w-2xl mt-1 text-sm ">
<p className="max-w-2xl mt-1 text-sm "> This information will be displayed publicly so be careful what you
This information will be displayed publicly so be careful what you share.
share. </p>
</p> </div>
</div> <div className="mt-6 space-y-6 sm:mt-5 sm:space-y-5">
<div className="mt-6 space-y-6 sm:mt-5 sm:space-y-5"> <div className="space-x-3 sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-t sm:pt-5">
<div className="space-x-3 sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-t sm:pt-5"> <HeadingSubComponent
<HeadingSubComponent title="Display Name"
title="Display Name" subHeading="The name you would like others to see you as."
subHeading="The name you would like others to see you as." />
/> <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 max-w-lg rounded-md shadow-sm">
<div className="flex max-w-lg rounded-md shadow-sm">
<InputText
id="displayname"
type="text"
labelTitle="Display name"
value={displayName}
updateFormValue={(v) => {
setDisplayName(v);
}}
showLabel={false}
/>
</div>
</div>
</div>
<div className="space-x-3 sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-t sm:pt-5">
<HeadingSubComponent
title="Email address"
subHeading="In case we need to get in touch with you"
/>
<div className="mt-1 sm:col-span-2 sm:mt-0">
<div className="flex max-w-lg rounded-md shadow-sm">
<InputText
id="email"
type="email"
labelTitle="Email address"
value={email}
updateFormValue={(v) => setEmail(v)}
showLabel={false}
/>
</div>
</div>
</div>
<div className="sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-t sm:pt-5">
<HeadingSubComponent
title="About"
subHeading="Tell us a little bit about yourself.. but not too much"
/>
<div className="mt-1 sm:col-span-2 sm:mt-0">
<InputText <InputText
id="about" id="displayName"
type="textarea" type="text"
labelTitle="About" label="Display name"
value={about} {...register("displayName")}
updateFormValue={(v) => setAbout(v)}
showLabel={false}
/> />
</div> </div>
</div> </div>
</div>
<div className="sm:grid sm:grid-cols-3 sm:items-center sm:gap-4 sm:border-t sm:pt-5"> <div className="space-x-3 sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-t sm:pt-5">
<HeadingSubComponent <HeadingSubComponent
title="Photo" title="Email address"
subHeading="Upload a picture to distinguish you from the rest" subHeading="In case we need to get in touch with you"
/> />
<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"> <div className="flex max-w-lg rounded-md shadow-sm">
<span className="w-12 h-12 overflow-hidden rounded-full"> <InputText
<svg id="email"
className="w-full h-full " type="email"
fill="currentColor" label="Email address"
viewBox="0 0 24 24" showLabel={false}
> {...register("email")}
<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>
</div> </div>
</div> </div>
</div>
<div className="sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-t sm:pt-5"> <div className="sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-t sm:pt-5">
<HeadingSubComponent <HeadingSubComponent
title="Cover photo" title="About"
subHeading="Upload a wide photo for the top of your profile page" subHeading="Tell us a little bit about yourself.. but not too much"
/>
<div className="mt-1 sm:col-span-2 sm:mt-0">
<InputTextArea
id="about"
type="textarea"
label="About"
showLabel={false}
{...register("about")}
/> />
<div className="mt-1 sm:col-span-2 sm:mt-0"> </div>
<div className="flex justify-center max-w-lg px-6 pt-5 pb-6 border-2 border-dashed rounded-md"> </div>
<div className="space-y-1 text-center">
<svg <div className="sm:grid sm:grid-cols-3 sm:items-center sm:gap-4 sm:border-t sm:pt-5">
className="w-12 h-12 mx-auto " <HeadingSubComponent
stroke="currentColor" title="Photo"
fill="none" subHeading="Upload a picture to distinguish you from the rest"
viewBox="0 0 48 48" />
aria-hidden="true" <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>
</div>
</div>
<div className="sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:border-t sm:pt-5">
<HeadingSubComponent
title="Cover photo"
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"
> >
<path <span>Upload a file</span>
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" <input
strokeWidth={2} id="file-upload"
strokeLinecap="round" name="file-upload"
strokeLinejoin="round" type="file"
className="sr-only"
/> />
</svg> </label>
<div className="flex text-sm "> <p className="pl-1">or drag and drop</p>
<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>
<p className="text-xs ">PNG, JPG, GIF up to 10MB</p>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div className="pt-5"> </div>
<div className="flex justify-end space-x-2">
<button type="button" className="btn-warning btn">
Cancel
</button>
<button type="submit" className="btn-success btn">
Save
</button>
</div>
</div>
</form>
); );
}; };

View File

@@ -1,63 +1,30 @@
import React from "react"; import React, { forwardRef } from "react";
import ITextInputProps from "./props";
interface IInputTextProps { const InputText = forwardRef<HTMLInputElement, ITextInputProps>(
id: string; ({ id, type, placeholder, label, showLabel = true, onChange, onBlur }, ref) => {
labelTitle: string;
labelStyle?: string;
type?: string;
containerStyle?: string;
value: string;
placeholder?: string;
showLabel?: boolean;
updateFormValue: (value: string) => void;
}
const InputText = ({
id,
labelTitle,
labelStyle,
type,
containerStyle,
value,
placeholder,
updateFormValue,
showLabel = true,
}: IInputTextProps) => {
const updateInputValue = (val: string) => { return (
updateFormValue(val); <React.Fragment>
}; {showLabel && (
const _getId = () => (type === "text" ? `inp_${id}` : `ta_${id}`); <label className="label" htmlFor={id}>
const _getInput = () => <span className="label-text">{label}</span>
type === "textarea" ? ( </label>
<textarea )}
id={_getId()} <input
name={_getId()} id={id}
rows={3} className="w-full input-bordered input"
className="w-full h-24 textarea-bordered textarea" type={type || "text"}
placeholder={placeholder || ""} placeholder={placeholder || ""}
defaultValue={value} onChange={onChange}
onChange={(e) => updateInputValue(e.target.value)} onBlur={onBlur}
/> ref={ref}
) : ( />
<input </React.Fragment>
id={_getId()}
className="w-full input-bordered input"
type={type || "text"}
placeholder={placeholder || ""}
value={value}
onChange={(e) => updateInputValue(e.target.value)}
/>
); );
return ( }
<React.Fragment> );
{showLabel && ( InputText.displayName = "InputTextAreaComponent";
<label className="label" htmlFor={_getId()}>
<span className="label-text">{labelTitle}</span>
</label>
)}
{_getInput()}
</React.Fragment>
);
};
export default InputText; export default InputText;

View File

@@ -0,0 +1,31 @@
import React, { forwardRef, Ref } from "react";
import ITextInputProps from "./props";
const InputTextArea = forwardRef<HTMLTextAreaElement, ITextInputProps>(
({ id, placeholder, showLabel = true, label, onChange, onBlur }, ref) => {
return (
<React.Fragment>
{showLabel && (
<label className="label" htmlFor={id}>
<span className="label-text">{label}</span>
</label>
)}
<textarea
id={id}
name={id}
rows={3}
className="w-full h-24 textarea-bordered textarea"
placeholder={placeholder || ""}
onChange={onChange}
onBlur={onBlur}
ref={ref}
/>
</React.Fragment>
);
}
);
InputTextArea.displayName = "InputTextComponent";
export default InputTextArea;

View File

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

View File

@@ -0,0 +1,13 @@
import { ChangeEventHandler } from "react";
export default interface ITextInputProps {
id: string;
type: string;
placeholder?: string;
showLabel?: boolean;
label: string;
name: string;
onChange: ChangeEventHandler<HTMLInputElement | HTMLTextAreaElement>;
onBlur: ChangeEventHandler<HTMLInputElement | HTMLTextAreaElement>;
}

View File

@@ -1,22 +1,25 @@
import React from "react"; import React from "react";
import { InputText } from "../inputs"; import "react-phone-number-input/style.css";
import { Control, UseFormRegister } from "react-hook-form";
import { ProfileForm } from "@/components/pages/profile/ProfilePageComponent";
import { DefaultFormValues } from "react-phone-number-input/react-hook-form";
import PhoneInputWithCountry from "react-phone-number-input/react-hook-form";
interface PhoneNumberNotificationControlProps { interface IPhoneNumberNotificationControlProps {
phoneNumber: string; name: string;
setPhoneNumber: (number: string) => void; control: any;
} }
const PhoneNumberNotificationControl = ({ const PhoneNumberNotificationControl = ({
phoneNumber, name,
setPhoneNumber, control,
}: PhoneNumberNotificationControlProps) => { }: IPhoneNumberNotificationControlProps) => {
return ( return (
<InputText <PhoneInputWithCountry
showLabel={false} name={name}
type="tel" className="w-full input-bordered input"
value="phoneNumber" defaultCountry="IE"
labelTitle="Phone number" placeholder="Enter phone number"
updateFormValue={(value) => setPhoneNumber(value)} control={control}
id="phone"
/> />
); );
}; };

View File

@@ -1,23 +1,26 @@
import React from "react"; import React from "react";
import { InputText } from "../inputs"; import InputText from "../inputs/InputText";
import { UseFormRegister } from "react-hook-form";
import { ProfileForm } from "@/components/pages/profile/ProfilePageComponent";
import { Profile } from "@/models";
interface ISignalNotificationProps { interface ISignalNotificationProps {
phoneNumber: string; register: UseFormRegister<ProfileForm>;
setPhoneNumber: (number: string) => void; profile: Profile;
} }
const SignalNotification = ({ const SignalNotification = ({
phoneNumber, register,
setPhoneNumber, profile,
}: ISignalNotificationProps) => { }: ISignalNotificationProps) => {
return ( return (
<InputText <div>
showLabel={false} {/* <InputText
type="tel" id="signalNotification"
value="phoneNumber" showLabel={false}
labelTitle="Phone number" type="tel"
updateFormValue={(value) => setPhoneNumber(value)} {...register('signalNotification')}
id="phone" /> */}
/> </div>
); );
}; };
export default SignalNotification; export default SignalNotification;

View File

@@ -1,17 +1,20 @@
import { useEffect, useState, useCallback } from "react"; import { useEffect, useState, useCallback } from "react";
import { import {
createUserWithEmailAndPassword, createUserWithEmailAndPassword,
EmailAuthCredential,
EmailAuthProvider,
FacebookAuthProvider, FacebookAuthProvider,
getAuth, getAuth,
GoogleAuthProvider, GoogleAuthProvider,
linkWithPopup, linkWithPopup,
OAuthCredential,
OAuthProvider, OAuthProvider,
onAuthStateChanged, onAuthStateChanged,
signInWithEmailAndPassword, signInWithEmailAndPassword,
signInWithPopup, signInWithPopup,
signOut, signOut,
TwitterAuthProvider, TwitterAuthProvider,
UserCredential UserCredential,
} from "firebase/auth"; } from "firebase/auth";
import { app } from "./firebase"; import { app } from "./firebase";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
@@ -20,8 +23,12 @@ import { users } from "../db";
import { doc, getDoc, setDoc } from "firebase/firestore"; import { doc, getDoc, setDoc } from "firebase/firestore";
import { servicesVersion } from "@ts-morph/common/lib/typescript"; import { servicesVersion } from "@ts-morph/common/lib/typescript";
import logger from "../util/logging"; import logger from "../util/logging";
import { debug } from "console";
import React from "react";
export default function useFirebaseAuth() { export default function useFirebaseAuth() {
const [errorCredential, setErrorCredential] =
useState<OAuthCredential | null>();
const [profile, setProfile] = useState<Profile | undefined>(); const [profile, setProfile] = useState<Profile | undefined>();
const auth = getAuth(app); const auth = getAuth(app);
const router = useRouter(); const router = useRouter();
@@ -40,17 +47,23 @@ export default function useFirebaseAuth() {
(savedProfile?.displayName || auth.currentUser.email) as string, (savedProfile?.displayName || auth.currentUser.email) as string,
(savedProfile?.photoURL || auth.currentUser.email) as string, (savedProfile?.photoURL || auth.currentUser.email) as string,
savedProfile?.about as string, savedProfile?.about as string,
savedProfile?.mobileNumber as string,
new Date(), new Date(),
savedProfile?.deviceRegistrations savedProfile?.deviceRegistrations
); );
setProfile(profile); setProfile(profile);
await setDoc(doc(users, auth.currentUser.uid), Object.assign({}, profile), { await setDoc(
merge: true doc(users, auth.currentUser.uid),
}); Object.assign({}, profile),
{
merge: true,
}
);
return profile; return profile;
} }
}, [auth.currentUser]); }, [auth.currentUser]);
const authStateChanged = useCallback(async (user: any) => { const authStateChanged = useCallback(
async (user: any) => {
if (user) { if (user) {
setLoading(true); setLoading(true);
const profile = await getUserProfile(); const profile = await getUserProfile();
@@ -70,10 +83,18 @@ export default function useFirebaseAuth() {
const signUp = async (email: string, password: string): Promise<string> => { const signUp = async (email: string, password: string): Promise<string> => {
try { try {
const response = await createUserWithEmailAndPassword(auth, email, password); const response = await createUserWithEmailAndPassword(
auth,
email,
password
);
logger.debug("useFireBaseAuth", "signUp_success", response); logger.debug("useFireBaseAuth", "signUp_success", response);
return ""; return "";
} catch (err: { code: string } | any) { } catch (err: { code: string } | any) {
const credential = GoogleAuthProvider.credentialFromError(err);
if (credential) {
setErrorCredential(credential as OAuthCredential);
}
logger.error("useFireBaseAuth", "signUp", err); logger.error("useFireBaseAuth", "signUp", err);
return err.code; return err.code;
} }
@@ -140,10 +161,30 @@ export default function useFirebaseAuth() {
await _processSignIn(provider); await _processSignIn(provider);
}; };
const linkAccounts = async (user: string, password: string) => {
debugger;
const credential = EmailAuthProvider.credential(user, password);
const provider = new GoogleAuthProvider();
const auth = getAuth();
if (!auth.currentUser) {
return;
}
linkWithPopup(auth.currentUser, provider)
.then((result) => {
// Accounts successfully linked.
debugger;
const credential = GoogleAuthProvider.credentialFromResult(result);
const user = result.user;
})
.catch((error) => {
logger.error("useFirebaseAuth", "linkWithPopup", error);
});
};
useEffect(() => { useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, authStateChanged); const unsubscribe = onAuthStateChanged(auth, authStateChanged);
return unsubscribe; return unsubscribe;
}, [auth, getUserProfile]); }, [auth, authStateChanged]);
return { return {
profile, profile,
@@ -154,6 +195,7 @@ export default function useFirebaseAuth() {
signInWithGoogle, signInWithGoogle,
signInWithTwitter, signInWithTwitter,
signInWithFacebook, signInWithFacebook,
getUserProfile linkAccounts,
getUserProfile,
}; };
} }

View File

@@ -7,7 +7,7 @@ import {
WithFieldValue, WithFieldValue,
QueryDocumentSnapshot, QueryDocumentSnapshot,
SnapshotOptions, SnapshotOptions,
Timestamp Timestamp,
} from "firebase/firestore"; } from "firebase/firestore";
const firebaseConfig = { const firebaseConfig = {
@@ -17,7 +17,7 @@ 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 const firebaseApp = initializeApp(firebaseConfig); export const firebaseApp = initializeApp(firebaseConfig);
const firestore = getFirestore(); const firestore = getFirestore();
@@ -30,7 +30,7 @@ const showConverter = {
...show, ...show,
date: show.date date: show.date
? Timestamp.fromDate(new Date(show.date as string)) ? Timestamp.fromDate(new Date(show.date as string))
: new Date() : new Date(),
}; };
}, },
fromFirestore( fromFirestore(
@@ -39,20 +39,44 @@ const showConverter = {
): Show { ): Show {
const data = snapshot.data(options)!; const data = snapshot.data(options)!;
return new Show(snapshot.id, data.title, data.date.toDate(), data.creator); return new Show(snapshot.id, data.title, data.date.toDate(), data.creator);
} },
};
const noticeConverter = {
toFirestore(notice: WithFieldValue<NotificationSchedule>): DocumentData {
return notice;
},
fromFirestore(
snapshot: QueryDocumentSnapshot,
options: SnapshotOptions
): NotificationSchedule {
const data = snapshot.data(options)!;
return new NotificationSchedule(
data.scheduleTimes.map((r: any) => r.toDate())
);
},
}; };
// Import all your model types // Import all your model types
import { Show, Reminder, RemindersProcessed, Profile } from "@/models"; import {
Show,
Reminder,
RemindersProcessed,
Profile,
NotificationSchedule,
} from "@/models";
// export all your collections // export all your collections
export const users = createCollection<Profile>("users"); export const users = createCollection<Profile>("users");
export const shows = export const shows =
createCollection<Show>("shows").withConverter(showConverter); createCollection<Show>("shows").withConverter(showConverter);
export const notificationSchedules =
createCollection<NotificationSchedule>("noticeSchedules").withConverter(
noticeConverter
);
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 };

15
src/lib/util/httpUtils.ts Normal file
View File

@@ -0,0 +1,15 @@
import axios from "axios";
import logger from "./logging";
const callWebHook = async (url: string, payload?: any): Promise<number> => {
try {
const response = await axios.post(url, payload);
return response.status;
} catch (err) {
logger.error("httpUtils", "callWebHook", err);
}
return 500;
};
export { callWebHook };

View File

@@ -1,24 +1,72 @@
import pino from 'pino' const logger = (() => {
import {logflarePinoVercel} from 'pino-logflare' const checkIfLogsEnabled = () => {
if (process.browser) {
const search = global?.window?.location?.search;
const enabled =
search && new URLSearchParams(search).get("debug") === "true";
const {stream, send} = logflarePinoVercel({ global.areLogsEnabled = enabled || false;
apiKey: "MK3qgU_-pwHQ", return global.areLogsEnabled;
sourceToken: "f7d8c11d-8f36-4981-8168-bfd69aa72bbf"
});
// create pino logger
const logger = pino({
browser: {
transmit: {
level: "info",
send: send,
} }
},
level: "debug",
base: {
env: process.env.NODE_ENV,
revision: process.env.VERCEL_GITHUB_COMMIT_SHA,
},
}, stream);
export default logger return false;
};
const isDev = process.env.NODE_ENV !== "production";
const print = (type, ...messages) => {
if (typeof global.areLogsEnabled === "undefined") {
checkIfLogsEnabled();
}
if (global.areLogsEnabled || isDev) {
switch (type) {
case "info":
console.info(
"%c Custom Log:",
"background: blue; color: white;",
...messages
);
break;
case "warn":
console.warn(
"%c Custom Log:",
"background: orange; color: white;",
...messages
);
break;
case "error":
console.error(
"%c Custom Log:",
"background: red; color: white;",
...messages
);
break;
case "trace":
console.trace(
"%c Custom Log:",
"background: grey; color: black;",
...messages
);
break;
case "debug":
default:
console.log(
"%c Custom Log:",
"background: green; color: white;",
...messages
);
}
}
};
return {
debug: print.bind(null, "debug"),
info: print.bind(null, "info"),
warn: print.bind(null, "warn"),
error: print.bind(null, "error"),
trace: print.bind(null, "trace"),
};
})();
export default logger;

View File

@@ -1,16 +1,37 @@
import logger from "@/lib/util/logging"; import logger from "@/lib/util/logging";
const twilio = require("twilio");
const sendSMS = async (number: string, body: string) => { const sendSMS = async (number: string, body: string) => {
const twilio = require("twilio"); const client = require("twilio")(
const client = require("twilio")(process.env.TWILIO_SID, process.env.TWILIO_AUTH_TOKEN); process.env.TWILIO_SID,
process.env.TWILIO_AUTH_TOKEN
);
const message = await client.messages const message = await client.messages.create({
.create({ body: body,
body: body, to: number, // Text this number
to: number, // Text this number from: process.env.TWILIO_FROM, // From a valid Twilio number
from: process.env.TWILIO_FROM // From a valid Twilio number });
});
logger.debug(`SMS sent to ${number}`, message); logger.debug(`SMS sent to ${number}`, message);
}; };
const sendWhatsApp = async (number: string, body: string): Promise<boolean> => {
const client = require("twilio")(
process.env.TWILIO_SID,
process.env.TWILIO_AUTH_TOKEN
);
try {
const message = await client.messages.create({
body: body,
from: `whatsapp:${process.env.WHATSAPP_NUMBER}`,
to: `whatsapp:${number}`,
});
logger.debug(`WhatsApp sent to ${number}`, message);
logger.debug("sms", "messageSent", message.sid);
return true;
} catch (err) {
logger.error("sms", "message failure", err);
}
return false;
};
export { sendSMS }; export { sendSMS, sendWhatsApp };

View File

@@ -0,0 +1,4 @@
const removeUndefinedProperties = (obj: any) => {
return JSON.parse(JSON.stringify(obj));
};
export { removeUndefinedProperties };

View File

@@ -1,5 +1,10 @@
export default interface DeviceRegistration { export default class DeviceRegistration {
deviceType: string; deviceType: string;
fcmToken: string; fcmToken: string;
lastSeen: Date; lastSeen: Date;
}; constructor(deviceType: string, fcmToken: string, lastSeen: Date) {
this.deviceType = deviceType;
this.fcmToken = fcmToken;
this.lastSeen = lastSeen;
}
}

View File

@@ -4,12 +4,14 @@ import Reminder from "./reminder";
import Notification from "./notification"; import Notification from "./notification";
import DeviceRegistration from "./deviceregistration"; import DeviceRegistration from "./deviceregistration";
import type { RemindersProcessed } from "./processes"; import type { RemindersProcessed } from "./processes";
import NotificationSchedule from "./notificationSchedule";
export { export {
Profile, Profile,
Show, Show,
Reminder, Reminder,
Notification, Notification,
type DeviceRegistration, DeviceRegistration,
NotificationSchedule,
RemindersProcessed RemindersProcessed
}; };

View File

@@ -0,0 +1,7 @@
export default class NotificationSchedule {
scheduleTimes: Date[];
constructor(scheduleTimes: Date[]) {
this.scheduleTimes = scheduleTimes;
}
}

View File

@@ -2,20 +2,23 @@ import DeviceRegistration from "@/models/deviceregistration";
export default class Profile { export default class Profile {
id: string; id: string;
email: string | null; email?: string;
displayName: string | null; displayName?: string;
photoURL: string | null; photoURL?: string;
headerPhotoURL?: string;
emailVerified: boolean = false; emailVerified: boolean = false;
about?: String; about?: string;
mobileNumber?: string;
lastSeen: Date; lastSeen: Date;
deviceRegistrations?: DeviceRegistration[] = []; deviceRegistrations?: DeviceRegistration[] = [];
constructor( constructor(
id: string, id: string,
email: string | null, email?: string,
displayName: string | null, displayName?: string,
photoURL: string | null, photoURL?: string,
about?: string, about?: string,
mobileNumber?: string,
lastSeen?: Date, lastSeen?: Date,
deviceRegistrations?: DeviceRegistration[] deviceRegistrations?: DeviceRegistration[]
) { ) {
@@ -24,6 +27,7 @@ export default class Profile {
this.displayName = displayName; this.displayName = displayName;
this.photoURL = photoURL; this.photoURL = photoURL;
this.about = about || ""; this.about = about || "";
this.mobileNumber = mobileNumber || "";
this.lastSeen = lastSeen || new Date(); this.lastSeen = lastSeen || new Date();
this.deviceRegistrations = deviceRegistrations || this.deviceRegistrations; this.deviceRegistrations = deviceRegistrations || this.deviceRegistrations;

View File

@@ -5,33 +5,56 @@ import { doc, setDoc } from "@firebase/firestore";
import { shows } from "@/lib/db"; import { shows } from "@/lib/db";
import { Show } from "@/models"; import { Show } from "@/models";
import Settings from "@/lib/db/settings"; import Settings from "@/lib/db/settings";
import { notificationSchedules } from "@/lib/db/index";
import { addSeconds } from "@/lib/util/dateUtils";
import { callWebHook } from "@/lib/util/httpUtils";
import { StatusCodes } from "http-status-codes";
const handler = async (req: NextApiRequest, res: NextApiResponse) => { const handler = async (req: NextApiRequest, res: NextApiResponse) => {
try { try {
logger.debug("API:cache", "Starting cache of events"); logger.debug("Starting sync of shows from google calendar");
const syncToken = await Settings.read("CalendarSyncToken"); const syncToken = await Settings.read("CalendarSyncToken");
logger.debug("API:cache", "Sync token", syncToken);
const e = await getCalendarEntries(syncToken); const e = await getCalendarEntries(syncToken);
logger.debug("API:cache", "Got events", e?.events);
if (!e?.events) { if (!e?.events) {
res.status(204).json({ result: "No calendar entries found" }); res
.status(StatusCodes.NO_CONTENT)
.json({ result: "No calendar entries found" });
} else { } else {
const entries = e?.events.map((r: any) => Show.fromJson(r)); const entries: Show[] = e?.events.map((r: any) => Show.fromJson(r));
logger.debug("API:cache", "Mapping events", entries);
for (const entry of entries) { for (const entry of entries) {
logger.debug("API:cache", "Mapping event", entry);
const showRef = doc(shows, entry.id); const showRef = doc(shows, entry.id);
await setDoc(showRef, { await setDoc(
title: entry.title, showRef,
date: entry.date, {
creator: entry.creator title: entry.title,
}, { merge: true }); date: entry.date,
creator: entry.creator,
},
{ merge: true }
);
const schedules = [
addSeconds(new Date(entry.date), -3600), //an hour before
addSeconds(new Date(entry.date), 0), //on time
];
const notificationSchedulRef = await setDoc(
doc(notificationSchedules, entry.id),
Object.assign({}, { scheduleTimes: schedules }),
{ merge: true }
);
} }
logger.debug("Stored show", res);
if (e?.syncToken) { if (e?.syncToken) {
await Settings.write("CalendarSyncToken", e?.syncToken); await Settings.write("CalendarSyncToken", e?.syncToken);
} }
res.status(200).json({ status: "OK", entries }); //ping the scheduler to let it know stuff has changed
if (entries.length > 0) {
const result = await callWebHook(
`${process.env.SCHEDULER_API_HOST}/job/reload`
);
if (result !== 200) {
logger.error("cache", "Unable to notify job scheduler");
}
}
res.status(StatusCodes.OK).json({ status: "OK", entries });
} }
} catch (err) { } catch (err) {
logger.error(err); logger.error(err);

View File

@@ -0,0 +1,36 @@
import type { NextApiRequest, NextApiResponse } from "next";
import { shows, users } from "@/lib/db";
import { Show } from "@/models";
import { doc, getDocs } from "@firebase/firestore";
import { getDoc, query, where } from "firebase/firestore";
import { sendWhatsApp } from "@/lib/util/notifications/sms";
import { StatusCodes } from "http-status-codes";
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
if (req.method !== "POST") {
return res.status(StatusCodes.METHOD_NOT_ALLOWED);
}
const { showId } = req.body;
const q = await getDoc(doc(shows, showId));
const show = Show.fromJson(q.data());
//iterate through every user and get their notifications
//and send 'em off
//TODO: Move this out of here
const usersQuery = await getDocs(
query(users, where("mobileNumber", "!=", null))
);
usersQuery.forEach(async (u) => {
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`;
await sendWhatsApp(user.mobileNumber, message);
}
});
res.status(StatusCodes.OK).json({ status: "OK" });
res.end();
};
export default handler;

View File

@@ -47,7 +47,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
} }
} }
} }
res.status(200).json({status: "OK"}); res.status(StatusCodes.OK).json({status: "OK"});
res.end(); res.end();
}; };
export default handler; export default handler;

View File

@@ -5,42 +5,43 @@ import { doc, getDocs, query, where } from "@firebase/firestore";
import { getDoc } from "firebase/firestore"; import { getDoc } from "firebase/firestore";
import { users } from "@/lib/db"; import { users } from "@/lib/db";
import { Profile, Reminder } from "@/models"; import { Profile, Reminder } from "@/models";
import { StatusCodes } from "http-status-codes";
const _getPayload = (message: string) => ({ const _getPayload = (message: string) => ({
notification: { notification: {
title: "Argle Bargle", title: "Argle Bargle",
body: `You are a ${message}`, body: `You are a ${message}`,
image: "https://otherway.fergl.ie/logo.png" image: "https://otherway.fergl.ie/logo.png",
}, },
data: { data: {
url: "https://google.com" url: "https://google.com",
} },
}); });
const handler = async (req: NextApiRequest, res: NextApiResponse) => { const handler = async (req: NextApiRequest, res: NextApiResponse) => {
// Send a message to the device corresponding to the provided // Send a message to the device corresponding to the provided
// registration token. // registration token.
try { try {
const results: any[] = []; const results: any[] = [];
const userResultSet = await getDocs(query(users, where("email", "==", "fergal.moran@gmail.com"))); const userResultSet = await getDocs(
query(users, where("email", "==", "fergal.moran@gmail.com"))
);
for (let u of userResultSet.docs) { for (let u of userResultSet.docs) {
const user = u.data() as Profile; const user = u.data() as Profile;
if (user?.deviceRegistrations) { if (user?.deviceRegistrations) {
for (const token of user?.deviceRegistrations) { for (const token of user?.deviceRegistrations) {
results.push(await firebaseAdmin results.push(
.messaging() await firebaseAdmin
.sendToDevice( .messaging()
token.fcmToken, .sendToDevice(token.fcmToken, _getPayload(token.deviceType))
_getPayload(token.deviceType))); );
} }
} }
} }
res.status(200).json({ results }); res.status(StatusCodes.OK).json({ results });
} catch (err) { } catch (err) {
logger.error("fcm", "sendPush", "Failed", err); logger.error("fcm", "sendPush", "Failed", err);
res.status(500); res.status(StatusCodes.INTERNAL_SERVER_ERROR);
} }
res.end(); res.end();
}; };

View File

@@ -0,0 +1,6 @@
import { StatusCodes } from "http-status-codes";
import { NextApiRequest, NextApiResponse } from "next";
export default function handler(req: NextApiRequest, res: NextApiResponse) {
res.status(StatusCodes.OK).end();
}

View File

@@ -0,0 +1,23 @@
import { sendWhatsApp } from "@/lib/util/notifications/sms";
import { StatusCodes } from "http-status-codes";
import { NextApiRequest, NextApiResponse } from "next";
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method !== "POST") {
return res.status(StatusCodes.METHOD_NOT_ALLOWED);
}
const { number, message } = req.body;
if (number && message) {
const result = await sendWhatsApp(number, message);
if (result) res.status(StatusCodes.OK);
else {
res.status(StatusCodes.INTERNAL_SERVER_ERROR);
}
} else {
res.status(StatusCodes.BAD_REQUEST);
}
res.end();
}

View File

@@ -1,23 +1,28 @@
import { NextApiRequest, NextApiResponse } from "next"; import { NextApiRequest, NextApiResponse } from "next";
import { reminders, shows } from "@/lib/db"; import { reminders } from "@/lib/db";
import { addDoc, doc, setDoc } from "@firebase/firestore"; import { doc, setDoc } from "@firebase/firestore";
import { StatusCodes } from "http-status-codes";
const handler = async (req: NextApiRequest, res: NextApiResponse) => { const handler = async (req: NextApiRequest, res: NextApiResponse) => {
if (req.method === "POST") { if (req.method !== "POST") {
res.status(StatusCodes.METHOD_NOT_ALLOWED);
} else {
const { userId, showId } = req.body; const { userId, showId } = req.body;
const docKey = `${userId}_${showId}`; const docKey = `${userId}_${showId}`;
const remindersRef = doc(reminders, docKey); const remindersRef = doc(reminders, docKey);
await setDoc(remindersRef, { await setDoc(
userId, remindersRef,
showId, {
notifications: [ userId,
{ secondsBefore: 60 * 60, destination: "353868065119" } //just set a single reminder for an hour beforehand showId,
] notifications: [
}, { merge: true }); { secondsBefore: 60 * 60, destination: "353868065119" }, //just set a single reminder for an hour beforehand
res.status(201); ],
} else { },
res.status(405); { merge: true }
);
} }
res.status(StatusCodes.CREATED);
res.end(); res.end();
}; };

View File

@@ -28,6 +28,6 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
creator: entry.creator creator: entry.creator
}, { merge: true }); }, { merge: true });
} }
res.status(200).json({ result: "We got pinged" }); res.status(StatusCodes.OK).json({ result: "We got pinged" });
res.end(); res.end();
}; };

View File

@@ -0,0 +1,16 @@
import { NextApiRequest, NextApiResponse } from "next";
import db, { notificationSchedules } from "@/lib/db";
import { getDocs, query, where } from "@firebase/firestore";
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const q = query(notificationSchedules);
const upcoming = await getDocs(q);
res.status(StatusCodes.OK).json(
upcoming.docs.map((r) => ({
showId: r.id,
...r.data(),
}))
);
res.end();
};
export default handler;

View File

@@ -8,7 +8,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
where("date", ">", new Date()) where("date", ">", new Date())
); );
const upcoming = await getDocs(q); const upcoming = await getDocs(q);
res.status(200).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;

View File

@@ -2,6 +2,6 @@ import { NextApiRequest, NextApiResponse } from "next";
import { setupCalendarWebhook } from "@/lib/util/google/calendarWatcher"; import { setupCalendarWebhook } from "@/lib/util/google/calendarWatcher";
const handler = async (req: NextApiRequest, res: NextApiResponse) => { const handler = async (req: NextApiRequest, res: NextApiResponse) => {
res.status(200).json({ hello: process.env }); res.status(StatusCodes.OK).json({ hello: process.env });
}; };
export default handler; export default handler;

298
yarn.lock
View File

@@ -560,21 +560,21 @@
integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==
"@jsdoc/salty@^0.2.1": "@jsdoc/salty@^0.2.1":
version "0.2.3" version "0.2.4"
resolved "https://registry.yarnpkg.com/@jsdoc/salty/-/salty-0.2.3.tgz#aab70c8756c1b98598bbc30867d3aa7a31b5c7d4" resolved "https://registry.yarnpkg.com/@jsdoc/salty/-/salty-0.2.4.tgz#049d92e1814b7fdffde2564ecd5746fb46278d8c"
integrity sha512-bbtCxCkxcnWhi50I+4Lj6mdz9w3pOXOgEQrID8TCZ/DF51fW7M9GCQW2y45SpBDdHd1Eirm1X/Cf6CkAAe8HPg== integrity sha512-HRBmslXHM6kpZOfGf0o41NUlGYGER0NoUBcT2Sik4rxzAN7f7+si7ad57SFSFpftvaMVnUaY7YlJuv3v5G80ZA==
dependencies: dependencies:
lodash "^4.17.21" lodash "^4.17.21"
"@next/env@13.2.1": "@next/env@13.2.2":
version "13.2.1" version "13.2.2"
resolved "https://registry.yarnpkg.com/@next/env/-/env-13.2.1.tgz#082d42cfc0c794e9185d7b4133d71440ba2e795d" resolved "https://registry.yarnpkg.com/@next/env/-/env-13.2.2.tgz#52761a775ad9cf241a6973bd72afba6ba10a4e0c"
integrity sha512-Hq+6QZ6kgmloCg8Kgrix+4F0HtvLqVK3FZAnlAoS0eonaDemHe1Km4kwjSWRE3JNpJNcKxFHF+jsZrYo0SxWoQ== integrity sha512-sBcFEJS8j2cNQemYy07TKUd8lSWj3/mzFA4GCTr/4T4LfYiw5Ep+PZ06AuFdR3z+jIZt9YqaXwUYi1J4p4yABQ==
"@next/eslint-plugin-next@13.1.5": "@next/eslint-plugin-next@13.2.2":
version "13.1.5" version "13.2.2"
resolved "https://registry.yarnpkg.com/@next/eslint-plugin-next/-/eslint-plugin-next-13.1.5.tgz#3eebea0835d2ec549a7b4b8976bf1d7796cca632" resolved "https://registry.yarnpkg.com/@next/eslint-plugin-next/-/eslint-plugin-next-13.2.2.tgz#410ea50276e0408c7565b499de702a274d6dd301"
integrity sha512-3kvLTX35bOWOCKU8KY74Ygczc55Qk/kB2TQy0tH7Rti6hzZ6Aij7WQ8zHdIVjmnlD0n/zXWXrIf5y56RKcLQkQ== integrity sha512-Kp4ThmQ4f5AybCB0RgxOWmKmAQmTj7/K9BT+ZzgHRJ25h3klO+MRSKC8yqiDcgl3qNVIjKwZi8D/thQGE7o3Mg==
dependencies: dependencies:
glob "7.1.7" glob "7.1.7"
@@ -583,70 +583,70 @@
resolved "https://registry.yarnpkg.com/@next/font/-/font-13.2.0.tgz#677b3fc67243bccbae94d7d1e96a494be98206c4" resolved "https://registry.yarnpkg.com/@next/font/-/font-13.2.0.tgz#677b3fc67243bccbae94d7d1e96a494be98206c4"
integrity sha512-pWoegIxqegV+9+gFmRCZao6xhA2m3kKS34lMXqShJ5ibRuyHkP/tfDE82LzYZmVQ3p51ZrkwTugNwy/ohiE2cA== integrity sha512-pWoegIxqegV+9+gFmRCZao6xhA2m3kKS34lMXqShJ5ibRuyHkP/tfDE82LzYZmVQ3p51ZrkwTugNwy/ohiE2cA==
"@next/swc-android-arm-eabi@13.2.1": "@next/swc-android-arm-eabi@13.2.2":
version "13.2.1" version "13.2.2"
resolved "https://registry.yarnpkg.com/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-13.2.1.tgz#67f2580fbbe05ee006220688972c5e3a555fc741" resolved "https://registry.yarnpkg.com/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-13.2.2.tgz#84561ccb52736a97fea82ce001f833eab4310115"
integrity sha512-Yua7mUpEd1wzIT6Jjl3dpRizIfGp9NR4F2xeRuQv+ae+SDI1Em2WyM9m46UL+oeW5GpMiEHoaBagr47RScZFmQ== integrity sha512-JHTnsNTl9gDQkWqggJFj6rQHK2+9lIMGolOPihfZBQAE48amVsGkYyFHbiuWnhwnYX99fCFWomARDwOtLAhzdQ==
"@next/swc-android-arm64@13.2.1": "@next/swc-android-arm64@13.2.2":
version "13.2.1" version "13.2.2"
resolved "https://registry.yarnpkg.com/@next/swc-android-arm64/-/swc-android-arm64-13.2.1.tgz#460a02b69eb23bb5f402266bcea9cadae59415c1" resolved "https://registry.yarnpkg.com/@next/swc-android-arm64/-/swc-android-arm64-13.2.2.tgz#aebb9664e7acf9996e02c7619ef536976a3b6bbb"
integrity sha512-Bifcr2f6VwInOdq1uH/9lp8fH7Nf7XGkIx4XceVd32LPJqG2c6FZU8ZRBvTdhxzXVpt5TPtuXhOP4Ij9UPqsVw== integrity sha512-j3zfjrojWuejU7bjucwBFviN9hQkQ1HqRVYMp8PDX82LXMc2pV4uZ3e9jQ8qk3OWsA1m+luaBK8o46NvS/Tetg==
"@next/swc-darwin-arm64@13.2.1": "@next/swc-darwin-arm64@13.2.2":
version "13.2.1" version "13.2.2"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.2.1.tgz#8b8530ff417802027471aee2419f78a58a863ccb" resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.2.2.tgz#233b450f92b0ed5f2c673666973c9165b8a82883"
integrity sha512-gvqm+fGMYxAkwBapH0Vvng5yrb6HTkIvZfY4oEdwwYrwuLdkjqnJygCMgpNqIFmAHSXgtlWxfYv1VC8sjN81Kw== integrity sha512-X66dKAq6IBmkirnsWyvncJ9i3kWkE2WrRbXGoFoEPNoyjgtV2qyLyk4ET9JIS0TJ2u01qODML6j4pKeqK8rNLA==
"@next/swc-darwin-x64@13.2.1": "@next/swc-darwin-x64@13.2.2":
version "13.2.1" version "13.2.2"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-13.2.1.tgz#80aebb3329a1e4568a28de1ee177780b3d50330c" resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-13.2.2.tgz#5d86717ab0b76072dd04d6e516eda0d5c510268f"
integrity sha512-HGqVqmaZWj6zomqOZUVbO5NhlABL0iIaxTmd0O5B0MoMa5zpDGoaHSG+fxgcWMXcGcxmUNchv1NfNOYiTKoHOg== integrity sha512-jsJKzVLlCNrG/Ot9Owv8QWUymAyS+Hcf/KQsXOYtL6PgvGqBMJ931SdaBpLOuyRD+sozTIRiAPCDwVgF/62PBQ==
"@next/swc-freebsd-x64@13.2.1": "@next/swc-freebsd-x64@13.2.2":
version "13.2.1" version "13.2.2"
resolved "https://registry.yarnpkg.com/@next/swc-freebsd-x64/-/swc-freebsd-x64-13.2.1.tgz#250ea2ab7e1734f22d11c677c463fab9ac33a516" resolved "https://registry.yarnpkg.com/@next/swc-freebsd-x64/-/swc-freebsd-x64-13.2.2.tgz#0caecb595eb63fe43d3346577116d5de7c19b9f1"
integrity sha512-N/a4JarAq+E+g+9K2ywJUmDIgU2xs2nA+BBldH0oq4zYJMRiUhL0iaN9G4e72VmGOJ61L/3W6VN8RIUOwTLoqQ== integrity sha512-oToMAjhdGWSgSOimKJ9GwPxOqoBIbbVUKoFGP5Imy5UGbbsnyV2jSLGhoi2/dH1Ko5X5d1R1la/X05xNB/p6ag==
"@next/swc-linux-arm-gnueabihf@13.2.1": "@next/swc-linux-arm-gnueabihf@13.2.2":
version "13.2.1" version "13.2.2"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-13.2.1.tgz#fe6bb29ed348a5f8ecae3740df22a8d8130c474a" resolved "https://registry.yarnpkg.com/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-13.2.2.tgz#db9578676d9dfd0e1e56070229d4453adf931f58"
integrity sha512-WaFoerF/eRbhbE57TaIGJXbQAERADZ/RZ45u6qox9beb5xnWsyYgzX+WuN7Tkhyvga0/aMuVYFzS9CEay7D+bw== integrity sha512-DSgXP2kkLt+oM3fRWaa2xPAUmtTnoa+GBfV5Czbgv4htY1BKClobp7Bj909TZUswvxXJtXiW8GrJp2To5lQfmw==
"@next/swc-linux-arm64-gnu@13.2.1": "@next/swc-linux-arm64-gnu@13.2.2":
version "13.2.1" version "13.2.2"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.2.1.tgz#4781b927fc5e421f3cea2b29e5d38e5e4837b198" resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.2.2.tgz#7c6a3832516b836d0610f6591b6522f318b725f9"
integrity sha512-R+Jhc1/RJTnncE9fkePboHDNOCm1WJ8daanWbjKhfPySMyeniKYRwGn5SLYW3S8YlRS0QVdZaaszDSZWgUcsmA== integrity sha512-HR1Nf7kz5NmijCiArJTz4bjJlbWyPA3JQ3SbxnPXDMPUkECBG1p0z6y2N2+oqNouMyOriNR6TSEgh7F2tASdZg==
"@next/swc-linux-arm64-musl@13.2.1": "@next/swc-linux-arm64-musl@13.2.2":
version "13.2.1" version "13.2.2"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.2.1.tgz#c2ba0a121b0255ba62450916bc70e6d0e26cbc98" resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.2.2.tgz#540b6a81f873e7bce9460e51d30aeef7a7c31199"
integrity sha512-oI1UfZPidGAVddlL2eOTmfsuKV9EaT1aktIzVIxIAgxzQSdwsV371gU3G55ggkurzfdlgF3GThFePDWF0d8dmw== integrity sha512-hDf08/yPZisKQ19lMHkVuQUmipmSq9QPzFmNDcV7e4QazTbIRwCC8J605EHedg+RGHS78CUwVhGQoQ2mbQ5y3w==
"@next/swc-linux-x64-gnu@13.2.1": "@next/swc-linux-x64-gnu@13.2.2":
version "13.2.1" version "13.2.2"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.2.1.tgz#573c220f8b087e5d131d1fba58d3e1a670b220ad" resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.2.2.tgz#daa394a4f288abd98cf535e1b0137322364bd1c7"
integrity sha512-PCygPwrQmS+7WUuAWWioWMZCzZm4PG91lfRxToLDg7yIm/3YfAw5N2EK2TaM9pzlWdvHQAqRMX/oLvv027xUiA== integrity sha512-9QeTU1YVhQXoVUOBXErJoExZR3XTovplMCTwB+RAtHu3oUGGs4KQFY8MoFUOmMA7CAJnKS+h1MD1T9/HDpS9PQ==
"@next/swc-linux-x64-musl@13.2.1": "@next/swc-linux-x64-musl@13.2.2":
version "13.2.1" version "13.2.2"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.2.1.tgz#950b5bb920b322ca7b447efbd12a9c7a10c3a642" resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.2.2.tgz#056c2fcd008e3302d829dfa739a0f017b4a01e5a"
integrity sha512-sUAKxo7CFZYGHNxheGh9nIBElLYBM6md/liEGfOTwh/xna4/GTTcmkGWkF7PdnvaYNgcPIQgHIMYiAa6yBKAVw== integrity sha512-SvyejahQLY79YJFXU27AcoDbmRqdK08t7HAf5LmocLdR5fxIneg7Oim/pwcuS/w8pYqDSAiCiMO0AAxSfO5jZQ==
"@next/swc-win32-arm64-msvc@13.2.1": "@next/swc-win32-arm64-msvc@13.2.2":
version "13.2.1" version "13.2.2"
resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.2.1.tgz#dbff3c4f5a3812a7059dac05804148a0f98682db" resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.2.2.tgz#3f188da59eef2ef1c99b695ea3c68cb3cb3f971f"
integrity sha512-qDmyEjDBpl/vBXxuOOKKWmPQOcARcZIMach1s7kjzaien0SySut/PHRlj56sosa81Wt4hTGhfhZ1R7g1n7+B8w== integrity sha512-Av46kUBp1qtJdk/HrIHZMIARE+jcMSJLHcrs/MkbHR1p4iEXf3JkZRrdQDUKZaz/VHDYc7Qw+vUv7zmgPZJbmw==
"@next/swc-win32-ia32-msvc@13.2.1": "@next/swc-win32-ia32-msvc@13.2.2":
version "13.2.1" version "13.2.2"
resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.2.1.tgz#7d2c17be7b8d9963984f5c15cc2588127101f620" resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.2.2.tgz#5873d4a7c29aff1ac75bea1f651fa9a332abb8a2"
integrity sha512-2joqFQ81ZYPg6DcikIzQn3DgjKglNhPAozx6dL5sCNkr1CPMD0YIkJgT3CnYyMHQ04Qi3Npv0XX3MD6LJO8OCA== integrity sha512-6HvAJY7yYfU+Ast3GPM8O+BsLGAdhZV0qUwHerJo5+Yzzx0VXW+OZTJ9O6RU01HBiMiqdz7SF3Or4Vca6WZKkQ==
"@next/swc-win32-x64-msvc@13.2.1": "@next/swc-win32-x64-msvc@13.2.2":
version "13.2.1" version "13.2.2"
resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.2.1.tgz#09713c6a925461f414e89422851326d1625bd4d2" resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.2.2.tgz#72c77c67eeafb393fbc744d530d763e5c356e0b5"
integrity sha512-r3+0fSaIZT6N237iMzwUhfNwjhAFvXjqB+4iuW+wcpxW+LHm1g/IoxN8eSRcb8jPItC86JxjAxpke0QL97qd6g== integrity sha512-7oNoiepUMf43+/4kUdpeGGkEBd3bOQwzo4O+bq/jslHb1IrQ7b8ZF1ODQyrDd661um6q5D+0sOtyRZJpo8e1jQ==
"@nodelib/fs.scandir@2.1.5": "@nodelib/fs.scandir@2.1.5":
version "2.1.5" version "2.1.5"
@@ -682,21 +682,21 @@
tslib "^2.4.0" tslib "^2.4.0"
"@prisma/client@^4.9.0": "@prisma/client@^4.9.0":
version "4.10.1" version "4.11.0"
resolved "https://registry.yarnpkg.com/@prisma/client/-/client-4.10.1.tgz#c47fd54661ee74b174cee63e9dc418ecf57a6ccd" resolved "https://registry.yarnpkg.com/@prisma/client/-/client-4.11.0.tgz#41d5664dea4172c954190a432f70b86d3e2e629b"
integrity sha512-VonXLJZybdt8e5XZH5vnIGCRNnIh6OMX1FS3H/yzMGLT3STj5TJ/OkMcednrvELgk8PK89Vo3aSh51MWNO0axA== integrity sha512-0INHYkQIqgAjrt7NzhYpeDQi8x3Nvylc2uDngKyFDDj1tTRQ4uV1HnVmd1sQEraeVAN63SOK0dgCKQHlvjL0KA==
dependencies: dependencies:
"@prisma/engines-version" "4.10.1-2.aead147aa326ccb985dcfed5b065b4fdabd44b19" "@prisma/engines-version" "4.11.0-57.8fde8fef4033376662cad983758335009d522acb"
"@prisma/engines-version@4.10.1-2.aead147aa326ccb985dcfed5b065b4fdabd44b19": "@prisma/engines-version@4.11.0-57.8fde8fef4033376662cad983758335009d522acb":
version "4.10.1-2.aead147aa326ccb985dcfed5b065b4fdabd44b19" version "4.11.0-57.8fde8fef4033376662cad983758335009d522acb"
resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-4.10.1-2.aead147aa326ccb985dcfed5b065b4fdabd44b19.tgz#312359d9d00e39e323136d0270876293d315658e" resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-4.11.0-57.8fde8fef4033376662cad983758335009d522acb.tgz#74af5ff56170c78e93ce46c56510160f58cd3c01"
integrity sha512-tsjTho7laDhf9EJ9EnDxAPEf7yrigSMDhniXeU4YoWc7azHAs4GPxRi2P9LTFonmHkJLMOLjR77J1oIP8Ife1w== integrity sha512-3Vd8Qq06d5xD8Ch5WauWcUUrsVPdMC6Ge8ILji8RFfyhUpqon6qSyGM0apvr1O8n8qH8cKkEFqRPsYjuz5r83g==
"@prisma/engines@4.10.1": "@prisma/engines@4.11.0":
version "4.10.1" version "4.11.0"
resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-4.10.1.tgz#c7062747f254e5d5fce98a8cae566c25f9f29fb2" resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-4.11.0.tgz#c99749bfe20f58e8f4d2b5e04fee0785eba440e1"
integrity sha512-B3tcTxjx196nuAu1GOTKO9cGPUgTFHYRdkPkTS4m5ptb2cejyBlH9X7GOfSt3xlI7p4zAJDshJP4JJivCg9ouA== integrity sha512-0AEBi2HXGV02cf6ASsBPhfsVIbVSDC9nbQed4iiY5eHttW9ZtMxHThuKZE1pnESbr8HRdgmFSa/Kn4OSNYuibg==
"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": "@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2":
version "1.1.2" version "1.1.2"
@@ -1201,6 +1201,11 @@ async-retry@^1.3.3:
dependencies: dependencies:
retry "0.13.1" retry "0.13.1"
asynckit@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
atomic-sleep@^1.0.0: atomic-sleep@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b" resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b"
@@ -1247,6 +1252,15 @@ axios@^0.26.1:
dependencies: dependencies:
follow-redirects "^1.14.8" follow-redirects "^1.14.8"
axios@^1.3.4:
version "1.3.4"
resolved "https://registry.yarnpkg.com/axios/-/axios-1.3.4.tgz#f5760cefd9cfb51fd2481acf88c05f67c4523024"
integrity sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==
dependencies:
follow-redirects "^1.15.0"
form-data "^4.0.0"
proxy-from-env "^1.1.0"
axobject-query@^3.1.1: axobject-query@^3.1.1:
version "3.1.1" version "3.1.1"
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-3.1.1.tgz#3b6e5c6d4e43ca7ba51c5babf99d22a9c68485e1" resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-3.1.1.tgz#3b6e5c6d4e43ca7ba51c5babf99d22a9c68485e1"
@@ -1421,7 +1435,7 @@ chokidar@^3.5.3:
optionalDependencies: optionalDependencies:
fsevents "~2.3.2" fsevents "~2.3.2"
classnames@^2.2.5, classnames@^2.3.2: classnames@^2.2.5, classnames@^2.3.1, classnames@^2.3.2:
version "2.3.2" version "2.3.2"
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924"
integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw== integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==
@@ -1490,6 +1504,13 @@ color@^4.2:
color-convert "^2.0.1" color-convert "^2.0.1"
color-string "^1.9.0" color-string "^1.9.0"
combined-stream@^1.0.8:
version "1.0.8"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
dependencies:
delayed-stream "~1.0.0"
commander@^5.0.0: commander@^5.0.0:
version "5.1.0" version "5.1.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae"
@@ -1553,6 +1574,11 @@ cosmiconfig@^7.0.1:
path-type "^4.0.0" path-type "^4.0.0"
yaml "^1.10.0" yaml "^1.10.0"
country-flag-icons@^1.5.4:
version "1.5.5"
resolved "https://registry.yarnpkg.com/country-flag-icons/-/country-flag-icons-1.5.5.tgz#04a41c83e2ea38ea28054d4e3eff9d1ce16aec1c"
integrity sha512-k4WXZ/WvWOSiYXRG1n8EYHNr1m/IX0GffKqAidaet5DrJsDOmJ8Q/8JvvONhZNnKYg24s4lvsm+9og1HcuIU/g==
cross-spawn@^7.0.2, cross-spawn@^7.0.3: cross-spawn@^7.0.2, cross-spawn@^7.0.3:
version "7.0.3" version "7.0.3"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
@@ -1672,6 +1698,11 @@ defined@^1.0.0:
resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.1.tgz#c0b9db27bfaffd95d6f61399419b893df0f91ebf" resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.1.tgz#c0b9db27bfaffd95d6f61399419b893df0f91ebf"
integrity sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q== integrity sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==
delayed-stream@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
depd@2.0.0: depd@2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
@@ -1745,9 +1776,9 @@ ee-first@1.1.1:
integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==
electron-to-chromium@^1.4.284: electron-to-chromium@^1.4.284:
version "1.4.312" version "1.4.314"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.312.tgz#e70a5b46252814ffc576b2c29032e1a559b9ad53" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.314.tgz#33e4ad7a2ca2ddbe2e113874cc0c0e2a00cb46bf"
integrity sha512-e7g+PzxzkbiCD1aNhdj+Tx3TLlfrQF/Lf+LAaUdoLvB1kCxf9wJimqXdWEqnoiYjFtxIR1hGBmoHsBIcCBNOMA== integrity sha512-+3RmNVx9hZLlc0gW//4yep0K5SYKmIvB5DXg1Yg6varsuAHlHwTeqeygfS8DWwLCsNOWrgj+p9qgM5WYjw1lXQ==
emoji-regex@^8.0.0: emoji-regex@^8.0.0:
version "8.0.0" version "8.0.0"
@@ -1919,12 +1950,12 @@ escodegen@^1.13.0:
optionalDependencies: optionalDependencies:
source-map "~0.6.1" source-map "~0.6.1"
eslint-config-next@13.1.5: eslint-config-next@^13.2.2:
version "13.1.5" version "13.2.2"
resolved "https://registry.yarnpkg.com/eslint-config-next/-/eslint-config-next-13.1.5.tgz#38daeaa1463197ef5b6cec9136ce6838ea776cf3" resolved "https://registry.yarnpkg.com/eslint-config-next/-/eslint-config-next-13.2.2.tgz#eee64ac5160b0741c11dbb6c848882b5f8da03c0"
integrity sha512-7FqkjkvGCQfvYUiPTFRiRYPR1uI6Ew+4f4mVp16lLSWcaChtWoZxQCZHM5n0yxzKKVmuEg1aM4uvDQfSXSjTww== integrity sha512-aI56IaeSHaLevpmD/5sVTRGDp7rs1vFkrJxFsrwCSkd4GbWHkUbam0cv8neoW58fw+O/B05MoK/Nb0rcf9wOZQ==
dependencies: dependencies:
"@next/eslint-plugin-next" "13.1.5" "@next/eslint-plugin-next" "13.2.2"
"@rushstack/eslint-patch" "^1.1.3" "@rushstack/eslint-patch" "^1.1.3"
"@typescript-eslint/parser" "^5.42.0" "@typescript-eslint/parser" "^5.42.0"
eslint-import-resolver-node "^0.3.6" eslint-import-resolver-node "^0.3.6"
@@ -2410,7 +2441,7 @@ flatted@^3.1.0:
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787" resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787"
integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==
follow-redirects@^1.14.0, follow-redirects@^1.14.8: follow-redirects@^1.14.0, follow-redirects@^1.14.8, follow-redirects@^1.15.0:
version "1.15.2" version "1.15.2"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13"
integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==
@@ -2422,6 +2453,15 @@ for-each@^0.3.3:
dependencies: dependencies:
is-callable "^1.1.3" is-callable "^1.1.3"
form-data@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.8"
mime-types "^2.1.12"
forwarded@0.2.0: forwarded@0.2.0:
version "0.2.0" version "0.2.0"
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
@@ -2778,6 +2818,11 @@ http-proxy-agent@^5.0.0:
agent-base "6" agent-base "6"
debug "4" debug "4"
http-status-codes@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/http-status-codes/-/http-status-codes-2.2.0.tgz#bb2efe63d941dfc2be18e15f703da525169622be"
integrity sha512-feERVo9iWxvnejp3SEfm/+oNG517npqL2/PIA8ORjyOZjGC7TwCRQsZylciLS64i6pJ0wRYz3rkXLRwbtFa8Ng==
https-proxy-agent@^5.0.0: https-proxy-agent@^5.0.0:
version "5.0.1" version "5.0.1"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6"
@@ -2851,6 +2896,13 @@ inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.4:
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
input-format@^0.3.8:
version "0.3.8"
resolved "https://registry.yarnpkg.com/input-format/-/input-format-0.3.8.tgz#9445b0cab2f0457fbe36d77d607e942fd37345c5"
integrity sha512-tLR0XRig1xIcG1PtIpMd/uoltvkAI62CN9OIbtj4/tEJAkqTCQLNHUZ9N4M46w0dopny7Rlt/lRH5Xzp7e6F+g==
dependencies:
prop-types "^15.8.1"
internal-slot@^1.0.3, internal-slot@^1.0.4: internal-slot@^1.0.3, internal-slot@^1.0.4:
version "1.0.5" version "1.0.5"
resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.5.tgz#f2a2ee21f668f8627a4667f309dc0f4fb6674986" resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.5.tgz#f2a2ee21f668f8627a4667f309dc0f4fb6674986"
@@ -3252,6 +3304,11 @@ levn@~0.3.0:
prelude-ls "~1.1.2" prelude-ls "~1.1.2"
type-check "~0.3.2" type-check "~0.3.2"
libphonenumber-js@^1.10.20:
version "1.10.21"
resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.10.21.tgz#860312cbec1389e36e28389161025c7817a3ae32"
integrity sha512-/udZhx49av2r2gZR/+xXSrwcR8smX/sDNrVpOFrvW+CA26TfYTVZfwb3MIDvmwAYMLs7pXuJjZX0VxxGpqPhsA==
lie@3.1.1: lie@3.1.1:
version "3.1.1" version "3.1.1"
resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e" resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e"
@@ -3434,7 +3491,7 @@ mime-db@1.52.0, "mime-db@>= 1.43.0 < 2":
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
mime-types@^2.0.8, mime-types@~2.1.24, mime-types@~2.1.34: mime-types@^2.0.8, mime-types@^2.1.12, mime-types@~2.1.24, mime-types@~2.1.34:
version "2.1.35" version "2.1.35"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
@@ -3518,30 +3575,30 @@ next-seo@^5.15.0:
resolved "https://registry.yarnpkg.com/next-seo/-/next-seo-5.15.0.tgz#b1a90508599774982909ea44803323c6fb7b50f4" resolved "https://registry.yarnpkg.com/next-seo/-/next-seo-5.15.0.tgz#b1a90508599774982909ea44803323c6fb7b50f4"
integrity sha512-LGbcY91yDKGMb7YI+28n3g+RuChUkt6pXNpa8FkfKkEmNiJkeRDEXTnnjVtwT9FmMhG6NH8qwHTelGrlYm9rgg== integrity sha512-LGbcY91yDKGMb7YI+28n3g+RuChUkt6pXNpa8FkfKkEmNiJkeRDEXTnnjVtwT9FmMhG6NH8qwHTelGrlYm9rgg==
next@^13.2.1: next@^13.2.2:
version "13.2.1" version "13.2.2"
resolved "https://registry.yarnpkg.com/next/-/next-13.2.1.tgz#34d823f518632b36379863228ed9f861c335b9c0" resolved "https://registry.yarnpkg.com/next/-/next-13.2.2.tgz#ee6e6ab1119fb685c598be7305725f2b5833d0e0"
integrity sha512-qhgJlDtG0xidNViJUPeQHLGJJoT4zDj/El7fP3D3OzpxJDUfxsm16cK4WTMyvSX1ciIfAq05u+0HqFAa+VJ+Hg== integrity sha512-dDKfGBqSxqmqx5WN9tDFg0uGUkD/LGUxR29tpe8AEmo2SwfbPWf04qyvDcKmpjt2fCzP4132BvFRZFlg+11kGw==
dependencies: dependencies:
"@next/env" "13.2.1" "@next/env" "13.2.2"
"@swc/helpers" "0.4.14" "@swc/helpers" "0.4.14"
caniuse-lite "^1.0.30001406" caniuse-lite "^1.0.30001406"
postcss "8.4.14" postcss "8.4.14"
styled-jsx "5.1.1" styled-jsx "5.1.1"
optionalDependencies: optionalDependencies:
"@next/swc-android-arm-eabi" "13.2.1" "@next/swc-android-arm-eabi" "13.2.2"
"@next/swc-android-arm64" "13.2.1" "@next/swc-android-arm64" "13.2.2"
"@next/swc-darwin-arm64" "13.2.1" "@next/swc-darwin-arm64" "13.2.2"
"@next/swc-darwin-x64" "13.2.1" "@next/swc-darwin-x64" "13.2.2"
"@next/swc-freebsd-x64" "13.2.1" "@next/swc-freebsd-x64" "13.2.2"
"@next/swc-linux-arm-gnueabihf" "13.2.1" "@next/swc-linux-arm-gnueabihf" "13.2.2"
"@next/swc-linux-arm64-gnu" "13.2.1" "@next/swc-linux-arm64-gnu" "13.2.2"
"@next/swc-linux-arm64-musl" "13.2.1" "@next/swc-linux-arm64-musl" "13.2.2"
"@next/swc-linux-x64-gnu" "13.2.1" "@next/swc-linux-x64-gnu" "13.2.2"
"@next/swc-linux-x64-musl" "13.2.1" "@next/swc-linux-x64-musl" "13.2.2"
"@next/swc-win32-arm64-msvc" "13.2.1" "@next/swc-win32-arm64-msvc" "13.2.2"
"@next/swc-win32-ia32-msvc" "13.2.1" "@next/swc-win32-ia32-msvc" "13.2.2"
"@next/swc-win32-x64-msvc" "13.2.1" "@next/swc-win32-x64-msvc" "13.2.2"
node-fetch@2.6.7: node-fetch@2.6.7:
version "2.6.7" version "2.6.7"
@@ -3986,11 +4043,11 @@ prettier@^2.8.3:
integrity sha512-vIS4Rlc2FNh0BySk3Wkd6xmwxB0FpOndW5fisM5H8hsZSxU2VWVB5CWIkIjWvrHjIhxk2g3bfMKM87zNTrZddw== integrity sha512-vIS4Rlc2FNh0BySk3Wkd6xmwxB0FpOndW5fisM5H8hsZSxU2VWVB5CWIkIjWvrHjIhxk2g3bfMKM87zNTrZddw==
prisma@^4.9.0: prisma@^4.9.0:
version "4.10.1" version "4.11.0"
resolved "https://registry.yarnpkg.com/prisma/-/prisma-4.10.1.tgz#88084695d7b364ae6bebf93d5006f84439c4e7d1" resolved "https://registry.yarnpkg.com/prisma/-/prisma-4.11.0.tgz#9695ba4129a43eab3e76b5f7a033c6c020377725"
integrity sha512-0jDxgg+DruB1kHVNlcspXQB9au62IFfVg9drkhzXudszHNUAQn0lVuu+T8np0uC2z1nKD5S3qPeCyR8u5YFLnA== integrity sha512-4zZmBXssPUEiX+GeL0MUq/Yyie4ltiKmGu7jCJFnYMamNrrulTBc+D+QwAQSJ01tyzeGHlD13kOnqPwRipnlNw==
dependencies: dependencies:
"@prisma/engines" "4.10.1" "@prisma/engines" "4.11.0"
process-warning@^1.0.0: process-warning@^1.0.0:
version "1.0.0" version "1.0.0"
@@ -4084,6 +4141,11 @@ proxy-addr@~2.0.7:
forwarded "0.2.0" forwarded "0.2.0"
ipaddr.js "1.9.1" ipaddr.js "1.9.1"
proxy-from-env@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
pseudomap@^1.0.1: pseudomap@^1.0.1:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
@@ -4165,7 +4227,7 @@ react-device-detect@^2.2.3:
dependencies: dependencies:
ua-parser-js "^1.0.33" ua-parser-js "^1.0.33"
react-dom@18.2.0: react-dom@^18.2.0:
version "18.2.0" version "18.2.0"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d"
integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g== integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==
@@ -4194,6 +4256,11 @@ react-firebase-hooks@^3.0.3:
resolved "https://registry.yarnpkg.com/react-firebase-hooks/-/react-firebase-hooks-3.0.5.tgz#4b0673ef4c028886a35c4a5c8def1a786d5e33e6" resolved "https://registry.yarnpkg.com/react-firebase-hooks/-/react-firebase-hooks-3.0.5.tgz#4b0673ef4c028886a35c4a5c8def1a786d5e33e6"
integrity sha512-9LztZGaZU0qJY/TQWzkBlfVrFmu0Ui6HE8CUjU7tNz9oGKGx5tUQG/xQbfyMIGClErgnnQsm8sC+oU9pg8Ri5Q== integrity sha512-9LztZGaZU0qJY/TQWzkBlfVrFmu0Ui6HE8CUjU7tNz9oGKGx5tUQG/xQbfyMIGClErgnnQsm8sC+oU9pg8Ri5Q==
react-hook-form@^7.43.2:
version "7.43.3"
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.43.3.tgz#780af64ea1f3c5864626a377e302bfcc7750af6f"
integrity sha512-LV6Fixh+hirrl6dXbM78aB6n//82aKbsNbcofF3wc6nx1UJLu3Jj/gsg1E5C9iISnLX+du8VTUyBUz2aCy+H7w==
react-hot-toast@^2.4.0: react-hot-toast@^2.4.0:
version "2.4.0" version "2.4.0"
resolved "https://registry.yarnpkg.com/react-hot-toast/-/react-hot-toast-2.4.0.tgz#b91e7a4c1b6e3068fc599d3d83b4fb48668ae51d" resolved "https://registry.yarnpkg.com/react-hot-toast/-/react-hot-toast-2.4.0.tgz#b91e7a4c1b6e3068fc599d3d83b4fb48668ae51d"
@@ -4211,7 +4278,18 @@ react-is@^16.13.1:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
react@18.2.0: react-phone-number-input@^3.2.19:
version "3.2.19"
resolved "https://registry.yarnpkg.com/react-phone-number-input/-/react-phone-number-input-3.2.19.tgz#78e09f12235b5c3a46555324198abd2c36b69d3e"
integrity sha512-3peWqyFgJ3+IvkB0GHvhAzQ6LEfJw1/obG8KPvCcac8OKp9Ir+pLdyySgxbD56EYkaAwZbBwOQJaGNqIX0yrtg==
dependencies:
classnames "^2.3.1"
country-flag-icons "^1.5.4"
input-format "^0.3.8"
libphonenumber-js "^1.10.20"
prop-types "^15.8.1"
react@^18.2.0:
version "18.2.0" version "18.2.0"
resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ== integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==