diff --git a/package.json b/package.json
index bf1e0f6c..35f5d255 100644
--- a/package.json
+++ b/package.json
@@ -13,6 +13,7 @@
"axios-cache-adapter": "^2.7.3",
"country-emoji": "^1.5.4",
"dompurify": "^2.2.7",
+ "firebase": "^11.2.0",
"htmlparser2": "^8.0.1",
"jsonpath": "^1.1.1",
"normalize.css": "^8.0.1",
diff --git a/public/auth.js b/public/auth.js
new file mode 100644
index 00000000..2f6f2d02
--- /dev/null
+++ b/public/auth.js
@@ -0,0 +1,6 @@
+window.addEventListener('message', (event) => {
+ if (event.data.type === 'TOKEN_RECEIVED') {
+ // Forward to content script
+ window.postMessage(event.data, '*')
+ }
+})
diff --git a/public/background.js b/public/background.js
index e7861b7e..df8301a7 100644
--- a/public/background.js
+++ b/public/background.js
@@ -1,9 +1,4 @@
-/*chrome.browserAction.onClicked.addListener(function (tab) {
- chrome.tabs.create({ url: "chrome://newtab" });
-}
-);*/
-
const uninstallUrl = `https://hackertab.dev/uninstall.html`
if (chrome.runtime.setUninstallURL) {
chrome.runtime.setUninstallURL(uninstallUrl)
-}
\ No newline at end of file
+}
diff --git a/public/base.manifest.json b/public/base.manifest.json
index 13f81829..6b0abd23 100644
--- a/public/base.manifest.json
+++ b/public/base.manifest.json
@@ -1,10 +1,19 @@
{
+ "manifest_version": 3,
"name": "Hackertab.dev - developer news",
"description": "All developer news in one tab",
"version": "1.20.1",
"chrome_url_overrides": {
"newtab": "index.html"
},
+ "host_permissions": ["https://*.hackertab.dev/*"],
+ "content_scripts": [
+ {
+ "matches": ["http://127.0.0.1:5173/*", "http://localhost:5173/*", "https://hackertab.dev/*"],
+ "run_at": "document_start",
+ "js": ["content.js"]
+ }
+ ],
"icons": {
"16": "/logos/logo16.png",
"32": "/logos/logo32.png",
diff --git a/public/chrome.manifest.json b/public/chrome.manifest.json
index 87acc9f8..fcb931e3 100644
--- a/public/chrome.manifest.json
+++ b/public/chrome.manifest.json
@@ -1,7 +1,5 @@
{
- "manifest_version": 3,
"background": {
"service_worker": "background.js"
- },
- "host_permissions": ["https://*.hackertab.dev/*"]
+ }
}
diff --git a/public/content.js b/public/content.js
new file mode 100644
index 00000000..21e9b99b
--- /dev/null
+++ b/public/content.js
@@ -0,0 +1,12 @@
+const script = document.createElement('script')
+script.src = chrome.runtime.getURL('auth.js')
+document.documentElement.appendChild(script)
+
+// Listen for messages from the injected script
+window.addEventListener('message', (event) => {
+ if (event.source !== window || !event.data || event.data.type !== 'TOKEN_RECEIVED') {
+ return
+ }
+
+ chrome.runtime.sendMessage({ ...event.data })
+})
diff --git a/public/firefox.manifest.json b/public/firefox.manifest.json
index db181686..769e74b6 100644
--- a/public/firefox.manifest.json
+++ b/public/firefox.manifest.json
@@ -1,17 +1,5 @@
{
- "manifest_version": 2,
- "background": {
- "scripts": [
- "background.js"
- ]
- },
- "permissions": [
- "https://*.hackertab.dev/*"
- ],
- "content_security_policy": "script-src 'self' object-src 'self'",
- "applications": {
- "gecko": {
- "id": "{f8793186-e9da-4332-aa1e-dc3d9f7bb04c}"
- }
- }
- }
\ No newline at end of file
+ "background": {
+ "scripts": ["background.js"]
+ }
+}
diff --git a/src/assets/App.css b/src/assets/App.css
index d460729b..e287471b 100644
--- a/src/assets/App.css
+++ b/src/assets/App.css
@@ -122,44 +122,25 @@ a {
}
.extras {
- display: none;
- flex-direction: row;
- align-content: center;
order: 2;
}
-.extraBtn {
- background-color: var(--button-background-color);
- color: var(--button-text-color);
- margin-left: 8px;
- height: 40px;
- width: 40px;
- line-height: 44px;
- font-size: 18px;
- text-align: center;
- border: 0;
- border-radius: 20px;
- cursor: pointer;
- position: relative;
+.buttonsFlex {
display: inline-flex;
- align-items: center;
- justify-content: center;
-}
-
-.extraBtn:first-child {
- margin-left: 0;
+ flex-direction: row;
+ align-content: center;
+ column-gap: 8px;
+ row-gap: 8px;
}
-.extraTextBtn {
- padding: 0 16px;
- width: auto;
- min-width: 40px;
+.dndButton {
font-weight: bold;
}
-.darkModeBtn {
- background-color: var(--dark-mode-background-color);
- color: var(--dark-mode-text-color);
+.profileImage {
+ height: 40px;
+ width: 40px;
+ border-radius: 20px;
}
.badgeCount {
@@ -1198,10 +1179,6 @@ Producthunt item
position: relative;
}
- .extras {
- display: block;
- }
-
.scrollButton {
align-items: center;
display: flex;
@@ -1349,6 +1326,135 @@ Producthunt item
color: var(--primary-text-color);
border-radius: 10px;
}
+
+.dangerToast {
+ background-color: #ff4d4f;
+ color: white;
+ padding: 10px 20px;
+ border-radius: 10px;
+}
+.successToast {
+ background-color: #52c41a;
+ color: white;
+ padding: 10px 20px;
+ border-radius: 10px;
+}
.capitalize {
text-transform: capitalize;
}
+
+/**
+Modal
+**/
+
+.Modal {
+ position: absolute;
+ left: 50%;
+ top: 50%;
+ transform: translate(-50%, -50%);
+ width: 650px;
+ background-color: var(--card-background-color);
+ padding: 24px;
+ border-radius: 10px;
+ box-shadow: 0 0 20px #00000052;
+ z-index: 3;
+ max-height: 80vh;
+ overflow-y: auto;
+}
+
+.Overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: var(--overlay-background-color);
+ backdrop-filter: blur(2px);
+}
+
+.modalHeader {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 16px;
+ position: relative;
+}
+
+.modalTitle {
+ margin: 0;
+ padding: 0;
+ color: var(--primary-text-color);
+ font-size: 1.3em;
+ display: inline-flex;
+ width: 100%;
+ column-gap: 8px;
+ align-items: center;
+}
+.modalCloseBtn {
+ align-items: center;
+ position: absolute;
+ background-color: transparent;
+ border-radius: 50%;
+ top: 0;
+ right: 0;
+ border: none;
+ color: var(--primary-text-color);
+ cursor: pointer;
+ display: flex;
+ justify-content: center;
+ margin: 0;
+ padding: 0;
+ text-align: center;
+}
+.modalCloseBtn:hover {
+ opacity: 0.7;
+}
+.settingContent .form {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ gap: 12px;
+}
+
+.settingContent input[type='text'] {
+ flex: 1;
+ background-color: var(--settings-input-background-color);
+ border: 1px solid var(--settings-input-border-color);
+ border-radius: 50px;
+ padding: 6px 18px;
+ color: var(--settings-input-text-color);
+ font-size: 14px;
+}
+
+.settingContent input[type='text']::placeholder {
+ /* Chrome, Firefox, Opera, Safari 10.1+ */
+ color: var(--settings-input-placeholder-color);
+ opacity: 1;
+ font-size: 14px;
+}
+
+.settingContent input[type='text']:focus {
+ border-color: var(--settings-input-border-focus-color);
+}
+@media (max-width: 768px) {
+ .Modal {
+ left: 0;
+ top: 0;
+ margin: 0;
+ height: 100vh;
+ max-height: 100vh;
+ transform: translate(0, 0);
+ border-radius: 0;
+ position: relative;
+ box-shadow: none;
+ width: auto;
+ }
+ .settingContent {
+ margin-top: 6px;
+ }
+ .settingRow {
+ flex-direction: column;
+ align-items: flex-start;
+ }
+}
diff --git a/src/assets/variables.css b/src/assets/variables.css
index e1d8ac7e..96541b82 100644
--- a/src/assets/variables.css
+++ b/src/assets/variables.css
@@ -31,7 +31,9 @@ html.dark {
/** Buttons **/
--button-background-color: #1c2026;
+ --button-hover-background-color: #181c21;
--button-text-color: #fff;
+ --button-hover-text-color: #d0d0d0;
/**Card **/
--card-background-color: #0d1116;
@@ -117,6 +119,8 @@ html.light {
--button-background-color: #d2deeb;
--button-text-color: #3c5065;
+ --button-hover-background-color: #b9cadc;
+ --button-hover-text-color: #3c5065;
/**Card **/
--card-background-color: #ffffff;
diff --git a/src/components/Elements/Button/Button.css b/src/components/Elements/Button/Button.css
new file mode 100644
index 00000000..ed30b5fe
--- /dev/null
+++ b/src/components/Elements/Button/Button.css
@@ -0,0 +1,87 @@
+.button {
+ background-color: var(--button-background-color);
+ color: var(--button-text-color);
+ font-size: 18px;
+ text-align: center;
+ border: 0;
+ border-radius: 50px;
+ cursor: pointer;
+ position: relative;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.5rem;
+
+ &:hover {
+ background-color: var(--button-hover-background-color);
+ color: var(--button-hover-text-color);
+ }
+
+ &.small {
+ padding: 0.3rem 0.75rem;
+ font-size: 0.875rem;
+ }
+
+ &.medium {
+ padding: 0.75rem 1rem;
+ font-size: 1.1rem;
+ height: 40px;
+ }
+
+ &.large {
+ padding: 1rem 2rem;
+ font-size: 1.25rem;
+ }
+
+ &.loading {
+ pointer-events: none;
+ cursor: not-allowed;
+ opacity: 0.8;
+ }
+}
+
+.circle-button {
+ border-radius: 50px;
+ cursor: pointer;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.5rem;
+ border: none;
+ padding: 0;
+ padding: 0;
+ font-size: 1.1em;
+ &.small {
+ min-width: 30px;
+ height: 30px;
+ }
+
+ &.medium {
+ min-width: 40px;
+ height: 40px;
+ }
+
+ &.large {
+ min-width: 50px;
+ height: 50px;
+ }
+
+ &.primary {
+ background-color: var(--button-background-color);
+ color: var(--button-text-color);
+
+ &:hover {
+ background-color: var(--button-hover-background-color);
+ color: var(--button-hover-text-color);
+ }
+ }
+
+ &.dark-focus {
+ background-color: var(--dark-mode-background-color);
+ color: var(--dark-mode-text-color);
+
+ &:hover {
+ opacity: 0.9;
+ }
+ }
+}
diff --git a/src/components/Elements/Button/Button.tsx b/src/components/Elements/Button/Button.tsx
new file mode 100644
index 00000000..bee7e6b4
--- /dev/null
+++ b/src/components/Elements/Button/Button.tsx
@@ -0,0 +1,38 @@
+import clsx from 'clsx'
+import React from 'react'
+import { Spinner } from '../Spinner'
+import './Button.css'
+const sizes = {
+ small: 'small',
+ medium: 'medium',
+ large: 'large',
+}
+type ButtonProps = {
+ children: React.ReactNode
+ onClick: () => void
+ className?: string
+ size?: keyof typeof sizes
+ startIcon?: React.ReactNode
+ endIcon?: React.ReactNode
+ isLoading?: boolean
+}
+export const Button = ({
+ size = 'medium',
+ onClick,
+ className,
+ startIcon,
+ endIcon,
+ children,
+ isLoading = false,
+}: ButtonProps) => {
+ return (
+
+ {isLoading ? : startIcon}
+ {children}
+ {endIcon}
+
+ )
+}
diff --git a/src/components/Elements/Button/CircleButton.tsx b/src/components/Elements/Button/CircleButton.tsx
new file mode 100644
index 00000000..c6877f3d
--- /dev/null
+++ b/src/components/Elements/Button/CircleButton.tsx
@@ -0,0 +1,36 @@
+import clsx from 'clsx'
+
+const sizes = {
+ small: 'small',
+ medium: 'medium',
+ large: 'large',
+}
+
+const variants = {
+ primary: 'primary',
+ darkfocus: 'dark-focus',
+}
+
+type CircleButtonProps = {
+ className?: string
+ onClick: () => void
+ size?: keyof typeof sizes
+ variant?: keyof typeof variants
+ children: React.ReactNode
+}
+
+export const CircleButton = ({
+ size = 'medium',
+ variant = 'primary',
+ onClick,
+ className,
+ children,
+}: CircleButtonProps) => {
+ return (
+
+ {children}
+
+ )
+}
diff --git a/src/components/Elements/Button/index.ts b/src/components/Elements/Button/index.ts
new file mode 100644
index 00000000..9a7dca3d
--- /dev/null
+++ b/src/components/Elements/Button/index.ts
@@ -0,0 +1,2 @@
+export * from './Button'
+export * from './CircleButton'
diff --git a/src/components/Elements/Modal/ConfirmModal.tsx b/src/components/Elements/Modal/ConfirmModal.tsx
new file mode 100644
index 00000000..a1e70b00
--- /dev/null
+++ b/src/components/Elements/Modal/ConfirmModal.tsx
@@ -0,0 +1,42 @@
+import { Button, Modal } from 'src/components/Elements'
+import './confirmModal.css'
+
+interface LogoutModalProps {
+ showModal: boolean
+ title: string
+ description: string
+ onClose: () => void
+ onConfirm: () => void
+}
+
+export const ConfirmModal = ({
+ showModal,
+ title,
+ description,
+ onClose,
+ onConfirm,
+}: LogoutModalProps) => {
+ return (
+
+
+
{description}
+
+
+ Cancel
+
+
+ Confirm
+
+
+
+
+ )
+}
diff --git a/src/components/Elements/Modal/Modal.tsx b/src/components/Elements/Modal/Modal.tsx
new file mode 100644
index 00000000..46515014
--- /dev/null
+++ b/src/components/Elements/Modal/Modal.tsx
@@ -0,0 +1,49 @@
+import clsx from 'clsx'
+import { VscClose } from 'react-icons/vsc'
+import ReactModal from 'react-modal'
+type ModalProps = {
+ showModal: boolean
+ onClose: () => void
+ className?: string
+ header: {
+ title: string
+ className?: string
+ icon?: React.ReactNode
+ }
+ children: React.ReactNode
+}
+export const Modal = ({ showModal, className, header, onClose, children }: ModalProps) => {
+ return (
+
+
+
+
+ {header.icon}
+ {header.title}
+
+ onClose()}
+ aria-label="Close share modal">
+
+
+
+
+ {children}
+
+
+ )
+}
diff --git a/src/components/Elements/Modal/confirmModal.css b/src/components/Elements/Modal/confirmModal.css
new file mode 100644
index 00000000..aa2a27f3
--- /dev/null
+++ b/src/components/Elements/Modal/confirmModal.css
@@ -0,0 +1,20 @@
+@media (min-width: 768px) {
+ .confirmModal {
+ width: 400px;
+ overflow-y: hidden;
+ }
+}
+
+.confirmModal .content {
+ display: flex;
+ flex-direction: column;
+}
+.confirmModal .description {
+ font-size: 1em;
+}
+.confirmModal .buttons {
+ display: flex;
+ flex-direction: row;
+ gap: 10px;
+ align-self: flex-end;
+}
diff --git a/src/components/Elements/Modal/index.ts b/src/components/Elements/Modal/index.ts
new file mode 100644
index 00000000..701e747d
--- /dev/null
+++ b/src/components/Elements/Modal/index.ts
@@ -0,0 +1,2 @@
+export * from './ConfirmModal'
+export * from './Modal'
diff --git a/src/components/Elements/Spinner/Spinner.css b/src/components/Elements/Spinner/Spinner.css
new file mode 100644
index 00000000..78e63a02
--- /dev/null
+++ b/src/components/Elements/Spinner/Spinner.css
@@ -0,0 +1,32 @@
+.spinner {
+ position: relative;
+ border-radius: 50%;
+ border: 2px solid transparent;
+ border-top-color: currentColor;
+ border-right-color: currentColor;
+ border-bottom-color: currentColor;
+ animation: spin 1.5s linear infinite;
+ &.small {
+ width: 20px;
+ height: 20px;
+ }
+
+ &.medium {
+ width: 40px;
+ height: 40px;
+ }
+
+ &.large {
+ width: 50px;
+ height: 50px;
+ }
+}
+
+@keyframes spin {
+ 0% {
+ transform: rotate(0deg);
+ }
+ 100% {
+ transform: rotate(360deg);
+ }
+}
diff --git a/src/components/Elements/Spinner/Spinner.tsx b/src/components/Elements/Spinner/Spinner.tsx
new file mode 100644
index 00000000..d55133cb
--- /dev/null
+++ b/src/components/Elements/Spinner/Spinner.tsx
@@ -0,0 +1,17 @@
+import clsx from 'clsx'
+import './Spinner.css'
+
+const sizes = {
+ small: 'small',
+ medium: 'medium',
+ large: 'large',
+}
+
+export type SpinnerProps = {
+ size?: keyof typeof sizes
+ className?: string
+}
+
+export const Spinner = ({ size = 'medium', className = '' }: SpinnerProps) => {
+ return
+}
diff --git a/src/components/Elements/Spinner/index.ts b/src/components/Elements/Spinner/index.ts
new file mode 100644
index 00000000..cf2b71e2
--- /dev/null
+++ b/src/components/Elements/Spinner/index.ts
@@ -0,0 +1 @@
+export * from './Spinner'
diff --git a/src/components/Elements/index.ts b/src/components/Elements/index.ts
index 850e09f7..14add55f 100644
--- a/src/components/Elements/index.ts
+++ b/src/components/Elements/index.ts
@@ -1,4 +1,5 @@
export * from './BottomNavigation'
+export * from './Button'
export * from './Card'
export * from './CardLink'
export * from './CardWithActions'
@@ -7,8 +8,10 @@ export * from './ClickableItem'
export * from './ColoredLanguagesBadges'
export * from './FloatingFilter'
export * from './InlineTextFilter'
+export * from './Modal'
export * from './Panel'
export * from './SearchBar'
export * from './SearchBarWithLogo'
+export * from './Spinner'
export * from './Steps'
export * from './UserTags'
diff --git a/src/components/Layout/AppLayout.tsx b/src/components/Layout/AppLayout.tsx
index 5d9fe243..1c0f7951 100644
--- a/src/components/Layout/AppLayout.tsx
+++ b/src/components/Layout/AppLayout.tsx
@@ -3,16 +3,21 @@ import 'react-contexify/dist/ReactContexify.css'
import { Outlet } from 'react-router-dom'
import { BeatLoader } from 'react-spinners'
import 'src/assets/App.css'
+import { AuthModal, useAuth } from 'src/features/auth'
import { MarketingBanner } from 'src/features/MarketingBanner'
+import { AuthProvider } from 'src/providers/AuthProvider'
import { Header } from './Header'
export const AppLayout = () => {
+ const { isAuthModalOpen } = useAuth()
+
return (
- <>
+
+
@@ -22,6 +27,6 @@ export const AppLayout = () => {
- >
+
)
}
diff --git a/src/components/Layout/Header.tsx b/src/components/Layout/Header.tsx
index 749928b7..7f53c028 100644
--- a/src/components/Layout/Header.tsx
+++ b/src/components/Layout/Header.tsx
@@ -1,19 +1,23 @@
import { useEffect, useState } from 'react'
import { BsFillBookmarksFill, BsFillGearFill, BsMoonFill } from 'react-icons/bs'
import { CgTab } from 'react-icons/cg'
+import { FaUser } from 'react-icons/fa'
import { IoMdSunny } from 'react-icons/io'
import { MdDoDisturbOff } from 'react-icons/md'
-import { RxArrowLeft } from 'react-icons/rx'
import { Link, useLocation, useNavigate } from 'react-router-dom'
import { ReactComponent as HackertabLogo } from 'src/assets/logo.svg'
import { SearchBar } from 'src/components/Elements/SearchBar'
import { UserTags } from 'src/components/Elements/UserTags'
+import { useAuth } from 'src/features/auth'
import { Changelog } from 'src/features/changelog'
import { identifyUserTheme, trackDNDDisable, trackThemeSelect } from 'src/lib/analytics'
import { useBookmarks } from 'src/stores/bookmarks'
import { useUserPreferences } from 'src/stores/preferences'
+import { Button, CircleButton } from '../Elements'
export const Header = () => {
+ const { openAuthModal, user, isConnected } = useAuth()
+
const [themeIcon, setThemeIcon] = useState( )
const { theme, setTheme, setDNDDuration, isDNDModeActive } = useUserPreferences()
const { userBookmarks } = useBookmarks()
@@ -74,38 +78,39 @@ export const Header = () => {
-
+
{isDNDModeActive() && (
-
onUnpauseClicked()}>
+
- Unpause
-
+ Unpause
+
)}
-
+
+
-
-
+
+
{themeIcon}
-
-
- <>
-
-
- >
-
+
+
navigate('/settings/bookmarks')}>
+
+
+
{
+ if (isConnected) {
+ navigate('/settings/general')
+ } else {
+ openAuthModal()
+ }
+ }}>
+ {isConnected ? (
+
+ ) : (
+
+ )}
+
- {location.pathname === '/' ? (
-
- ) : (
-
-
- Back
-
-
- )}
+ {location.pathname === '/' &&
}
>
)
diff --git a/src/config/index.tsx b/src/config/index.tsx
index 2f5f622b..d0553ca6 100644
--- a/src/config/index.tsx
+++ b/src/config/index.tsx
@@ -3,6 +3,9 @@ export const ANALYTICS_ENDPOINT = import.meta.env.VITE_AMPLITUDE_URL as string
export const ANALYTICS_SDK_KEY = import.meta.env.VITE_AMPLITUDE_KEY as string
export const API_ENDPOINT = import.meta.env.VITE_API_URL as string
export const LS_ANALYTICS_ID_KEY = 'hackerTabAnalyticsId'
+export const FIREBASE_API_KEY = import.meta.env.VITE_FIREBASE_API_KEY as string
+export const BUILD_TARGET = (import.meta.env.VITE_BUILD_TARGET as 'web' | 'extension') || 'web'
+
// Meta
export const name = 'Hackertab.dev'
export const slogan = '— Stay updated with the new technology and trends'
diff --git a/src/features/auth/api/getOauthLink.ts b/src/features/auth/api/getOauthLink.ts
new file mode 100644
index 00000000..3785f8bb
--- /dev/null
+++ b/src/features/auth/api/getOauthLink.ts
@@ -0,0 +1,26 @@
+import { useMutation } from '@tanstack/react-query'
+import { axios } from 'src/lib/axios'
+import { MutationConfig } from 'src/lib/react-query'
+
+export type GetOauthLinkDTO = {
+ data: {
+ provider: string
+ state: string
+ }
+}
+const getOauthLink = ({ data }: GetOauthLinkDTO): Promise<{ authLink: string }> => {
+ return axios.post('/engine/auth/oauth-link', data)
+}
+
+type QueryFnType = typeof getOauthLink
+
+type UseGetArticlesOptions = {
+ config?: MutationConfig
+}
+
+export const useGetOauthLink = ({ config }: UseGetArticlesOptions = {}) => {
+ return useMutation({
+ ...config,
+ mutationFn: getOauthLink,
+ })
+}
diff --git a/src/features/auth/components/AuthModal.tsx b/src/features/auth/components/AuthModal.tsx
new file mode 100644
index 00000000..e5e2c269
--- /dev/null
+++ b/src/features/auth/components/AuthModal.tsx
@@ -0,0 +1,130 @@
+import { AuthProvider, GithubAuthProvider, GoogleAuthProvider } from 'firebase/auth'
+import { useCallback, useState } from 'react'
+import { FaGithub } from 'react-icons/fa'
+import { FcGoogle } from 'react-icons/fc'
+import { IoHeartCircle } from 'react-icons/io5'
+import { Button, Modal } from 'src/components/Elements'
+import { BUILD_TARGET, privacyPolicyLink, termsAndConditionsLink } from 'src/config'
+import { useAuth } from 'src/features/auth'
+import { getBrowserName } from 'src/utils/Environment'
+import { checkHostPermissions, requestHostPermissions } from 'src/utils/Permissions'
+import { useGetOauthLink } from '../api/getOauthLink'
+import './authModal.css'
+
+type AuthModalProps = {
+ showAuth: boolean
+}
+
+const googleAuthProvider = new GoogleAuthProvider()
+const githubAuthProvider = new GithubAuthProvider()
+
+export const AuthModal = ({ showAuth }: AuthModalProps) => {
+ const { closeAuthModal, authError, setAuthError } = useAuth()
+ const [selectedProvider, setSelectedProvider] = useState(googleAuthProvider)
+ const getOauthLink = useGetOauthLink()
+
+ const requestOauthLink = useCallback(
+ async (provider: AuthProvider) => {
+ getOauthLink
+ .mutateAsync({
+ data: {
+ provider: provider.providerId,
+ state: BUILD_TARGET === 'web' ? window.location.origin : getBrowserName(),
+ },
+ })
+ .then(({ authLink }) => {
+ window.open(authLink, BUILD_TARGET === 'web' ? '_self' : '_blank')
+ })
+ },
+ [getOauthLink]
+ )
+
+ const signIn = useCallback(async (provider: AuthProvider) => {
+ setSelectedProvider(provider)
+ setAuthError(null)
+
+ if (BUILD_TARGET === 'web') {
+ requestOauthLink(provider)
+ } else {
+ const permissionCheck = await checkHostPermissions()
+
+ if (!permissionCheck) {
+ setAuthError({
+ message: 'Hackertab needs permission to Sign in, Please request it and try again',
+ })
+ } else {
+ requestOauthLink(provider)
+ }
+ }
+ }, [])
+
+ const requestMissingPermission = useCallback(async () => {
+ const permissionRequest = await requestHostPermissions()
+
+ if (!permissionRequest) {
+ setAuthError({
+ message: 'Permission not granted, Request it again',
+ })
+ } else {
+ setAuthError(null)
+ requestOauthLink(selectedProvider)
+ }
+ }, [])
+
+ return (
+ ,
+ }}
+ className="authModal">
+
+
Create an account to sync, save bookmarks, and earn rewards.
+
+ }
+ isLoading={getOauthLink.isLoading && getOauthLink.data?.['provider'] === 'github.com'}
+ onClick={() => {
+ signIn(githubAuthProvider)
+ }}
+ size="medium">
+ Connect with Github
+
+ }
+ onClick={() => signIn(googleAuthProvider)}
+ size="medium">
+ Connect with Google
+
+
+ {authError && (
+
+
{authError.message}
+ {authError.retry && (
+
requestMissingPermission()} size="small" className="cta">
+ {authError.retry.label}
+
+ )}
+
+ )}
+
+
+
+ )
+}
diff --git a/src/features/auth/components/authModal.css b/src/features/auth/components/authModal.css
new file mode 100644
index 00000000..3007d414
--- /dev/null
+++ b/src/features/auth/components/authModal.css
@@ -0,0 +1,46 @@
+@media (min-width: 768px) {
+ .authModal {
+ width: 400px;
+ }
+}
+.authModal .header {
+ text-align: center;
+ justify-content: center;
+}
+.authModal .buttons {
+ display: inline-flex;
+ align-items: center;
+ justify-items: center;
+ flex-direction: column;
+ row-gap: 16px;
+ width: 100%;
+ margin: 0 0 20px 0;
+}
+
+.authModal .description {
+ padding: 20px;
+ text-align: center;
+ font-size: 1em;
+}
+.authModal .errors {
+ background-color: rgba(255, 0, 0, 0.064);
+ border: 1px solid rgba(255, 0, 0, 0.363);
+ border-radius: 6px;
+ padding: 8px;
+ display: flex;
+ flex-direction: row;
+ p {
+ padding: 0;
+ margin: 0;
+ font-size: 0.8em;
+ color: red;
+ }
+ .cta {
+ flex: 1;
+ }
+}
+
+.authModal .footer {
+ font-size: 0.7em;
+ text-align: center;
+}
diff --git a/src/features/auth/hooks/useAuth.ts b/src/features/auth/hooks/useAuth.ts
new file mode 100644
index 00000000..4d808d79
--- /dev/null
+++ b/src/features/auth/hooks/useAuth.ts
@@ -0,0 +1,28 @@
+import { signOut } from 'firebase/auth'
+import { AuthModalStore, AuthStore } from 'src/features/auth'
+import { trackUserDisconnect } from 'src/lib/analytics'
+import { firebaseAuth } from 'src/lib/firebase'
+
+export const useAuth = () => {
+ const authModalStore = AuthModalStore()
+ const authStore = AuthStore()
+ const { user, providerId, initState, clear } = authStore
+
+ const isConnected = user != null
+
+ const logout = async () => {
+ trackUserDisconnect()
+ signOut(firebaseAuth)
+ clear()
+ return await firebaseAuth.signOut()
+ }
+
+ return {
+ ...authModalStore,
+ initState,
+ isConnected,
+ logout,
+ user,
+ providerId,
+ }
+}
diff --git a/src/features/auth/index.ts b/src/features/auth/index.ts
new file mode 100644
index 00000000..a93cd5d3
--- /dev/null
+++ b/src/features/auth/index.ts
@@ -0,0 +1,5 @@
+export * from './components/AuthModal'
+export * from './hooks/useAuth'
+export * from './stores/authModalStore'
+export * from './stores/authStore'
+export * from './types'
diff --git a/src/features/auth/stores/authModalStore.ts b/src/features/auth/stores/authModalStore.ts
new file mode 100644
index 00000000..d68d5642
--- /dev/null
+++ b/src/features/auth/stores/authModalStore.ts
@@ -0,0 +1,23 @@
+import { create } from 'zustand'
+
+type AuthError = {
+ message: string
+ retry?: {
+ label: string
+ } | null
+}
+interface AuthModalState {
+ isAuthModalOpen: boolean
+ authError: AuthError | null
+ openAuthModal: () => void
+ closeAuthModal: () => void
+ setAuthError: (error: AuthError | null) => void
+}
+
+export const AuthModalStore = create((set) => ({
+ isAuthModalOpen: false,
+ authError: null,
+ setAuthError: (error) => set({ authError: error }),
+ openAuthModal: () => set({ isAuthModalOpen: true }),
+ closeAuthModal: () => set({ isAuthModalOpen: false }),
+}))
diff --git a/src/features/auth/stores/authStore.ts b/src/features/auth/stores/authStore.ts
new file mode 100644
index 00000000..b9c30e95
--- /dev/null
+++ b/src/features/auth/stores/authStore.ts
@@ -0,0 +1,31 @@
+import { User } from 'src/features/auth/types'
+import { create } from 'zustand'
+import { persist } from 'zustand/middleware'
+
+type AuthState = {
+ user: User | null
+ providerId: string | null
+}
+
+type AuthActions = {
+ initState: (state: AuthState) => void
+ clear: () => void
+}
+
+export const AuthStore = create(
+ persist(
+ (set) => ({
+ user: null,
+ providerId: null,
+ initState: (newState: AuthState) =>
+ set({
+ user: newState.user,
+ providerId: newState.providerId,
+ }),
+ clear: () => set({ user: null }),
+ }),
+ {
+ name: 'auth-storage', // key in localStorage
+ }
+ )
+)
diff --git a/src/features/auth/types/index.ts b/src/features/auth/types/index.ts
new file mode 100644
index 00000000..a84ee5da
--- /dev/null
+++ b/src/features/auth/types/index.ts
@@ -0,0 +1,4 @@
+export type User = {
+ name: string
+ imageURL?: string
+}
diff --git a/src/features/settings/components/AddSearchEngine.tsx b/src/features/settings/components/AddSearchEngine.tsx
index 48fb8d26..c3613947 100644
--- a/src/features/settings/components/AddSearchEngine.tsx
+++ b/src/features/settings/components/AddSearchEngine.tsx
@@ -2,6 +2,7 @@ import { useState } from 'react'
import { isValidURL } from 'src/utils/UrlUtils'
import { TiPlus } from 'react-icons/ti'
+import { Button } from 'src/components/Elements'
import { useUserPreferences } from 'src/stores/preferences'
export const AddSearchEngine = () => {
@@ -45,9 +46,9 @@ export const AddSearchEngine = () => {
placeholder="https://google.com?q="
/>
-
- Add
-
+ } size="small" onClick={onAddSearchEngine}>
+ Add
+
{RssInputFeedback && (
diff --git a/src/features/settings/components/BookmarkSettings/BookmarkSettings.tsx b/src/features/settings/components/BookmarkSettings/BookmarkSettings.tsx
index 3683e9ed..b27d8ce6 100644
--- a/src/features/settings/components/BookmarkSettings/BookmarkSettings.tsx
+++ b/src/features/settings/components/BookmarkSettings/BookmarkSettings.tsx
@@ -2,7 +2,7 @@ import { useRef } from 'react'
import { BiBookmarkMinus } from 'react-icons/bi'
import { RiFileDownloadFill, RiFileUploadFill } from 'react-icons/ri'
import toast from 'react-simple-toasts'
-import { CardLink } from 'src/components/Elements'
+import { Button, CardLink, CircleButton } from 'src/components/Elements'
import { SettingsContentLayout } from 'src/components/Layout/SettingsContentLayout'
import { SUPPORTED_CARDS } from 'src/config/supportedCards'
import { BookmarkedPost } from 'src/features/bookmarks'
@@ -15,7 +15,8 @@ type BookmarkItemProps = {
item: BookmarkedPost
appendRef?: boolean
}
-const BookmarkItem = ({ item, appendRef = false }: BookmarkItemProps) => {
+
+export const BookmarkItem = ({ item, appendRef = false }: BookmarkItemProps) => {
const { unbookmarkPost } = useBookmarks()
const { userCustomCards } = useUserPreferences()
@@ -114,7 +115,7 @@ export const BookmarkSettings = () => {
title="Bookmarks"
description="Find all your bookmarks here. You can remove a bookmark by clicking on the remove icon."
actions={
- <>
+
{
className="hidden"
onChange={handleFileChange}
/>
- importBookmarks()}>
+
- Import
-
- exportBookmarks()}>
+ Import
+
+ exportBookmarks()}>
-
- >
+
+
}>
{userBookmarks.map((bm) => (
diff --git a/src/features/settings/components/GeneralSettings/DNDSettings.tsx b/src/features/settings/components/GeneralSettings/DNDSettings.tsx
index 2385ec01..1ac8a1b5 100644
--- a/src/features/settings/components/GeneralSettings/DNDSettings.tsx
+++ b/src/features/settings/components/GeneralSettings/DNDSettings.tsx
@@ -1,6 +1,7 @@
import { useState } from 'react'
import { useNavigate } from 'react-router-dom'
import Select, { SingleValue } from 'react-select'
+import { Button } from 'src/components/Elements'
import { trackDNDEnable } from 'src/lib/analytics'
import { useUserPreferences } from 'src/stores/preferences'
import { diffBetweenTwoDatesInMinutes } from 'src/utils/DateUtils'
@@ -101,7 +102,9 @@ export const DNDSettings = () => {
/>
- Apply
+
+ Apply
+
{timeoutLabel() && (
diff --git a/src/features/settings/components/GeneralSettings/GeneralSettings.tsx b/src/features/settings/components/GeneralSettings/GeneralSettings.tsx
index 0915c0ef..48d950ab 100644
--- a/src/features/settings/components/GeneralSettings/GeneralSettings.tsx
+++ b/src/features/settings/components/GeneralSettings/GeneralSettings.tsx
@@ -1,9 +1,12 @@
-import React from 'react'
+import React, { useState } from 'react'
+import { FaGithub } from 'react-icons/fa'
+import { FcGoogle } from 'react-icons/fc'
import Toggle from 'react-toggle'
import 'react-toggle/style.css'
-import { ChipsSet } from 'src/components/Elements'
+import { Button, ChipsSet, ConfirmModal } from 'src/components/Elements'
import { Footer } from 'src/components/Layout'
import { SettingsContentLayout } from 'src/components/Layout/SettingsContentLayout'
+import { useAuth, User } from 'src/features/auth'
import {
identifyUserLinksInNewTab,
identifyUserListingMode,
@@ -19,12 +22,51 @@ import { Option } from 'src/types'
import { DNDSettings } from './DNDSettings'
import './generalSettings.css'
+// TODO Maybe we should create a separate folder in components for UserInfo ?
+interface UserInfoProps {
+ user: User
+}
+
+const UserInfo = ({ user }: UserInfoProps) => {
+ const { logout, providerId } = useAuth()
+ const providerName = providerId?.split('.')[0] || 'Unknown'
+ const [showLogout, setShowLogout] = useState(false)
+
+ return (
+
+
setShowLogout(false)}
+ onConfirm={logout}
+ />
+ {user?.imageURL && }
+
+
{user.name}
+
+ {providerId == 'github.com' ? (
+
+ ) : providerId == 'google.com' ? (
+
+ ) : null}
+ Connected with {providerName}
+
+
+ setShowLogout(true)} size="small">
+ Logout
+
+
+
+
+ )
+}
+
export const GeneralSettings = () => {
const {
openLinksNewTab,
listingMode,
theme,
- searchEngine,
maxVisibleCards,
setTheme,
setListingMode,
@@ -62,6 +104,8 @@ export const GeneralSettings = () => {
}
}
+ const { user } = useAuth()
+
return (
{
'Customize your experience by selecting the number of cards you want to see, the search engine you want to use and more.'
}>
+ {user != null &&
}
Max number of cards to display
diff --git a/src/features/settings/components/GeneralSettings/generalSettings.css b/src/features/settings/components/GeneralSettings/generalSettings.css
index 81a3b7dd..215e0bf8 100644
--- a/src/features/settings/components/GeneralSettings/generalSettings.css
+++ b/src/features/settings/components/GeneralSettings/generalSettings.css
@@ -1,32 +1,3 @@
-/**
-Modal
-**/
-
-.Modal {
- position: absolute;
- left: 50%;
- top: 50%;
- transform: translate(-50%, -50%);
- width: 650px;
- background-color: var(--card-background-color);
- padding: 24px;
- border-radius: 10px;
- box-shadow: 0 0 20px #00000052;
- z-index: 3;
- max-height: 80vh;
- overflow-y: scroll;
-}
-
-.Overlay {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background-color: var(--overlay-background-color);
- backdrop-filter: blur(2px);
-}
-
.settingTitle {
width: 300px;
margin: 0;
@@ -45,34 +16,6 @@ Modal
border-bottom: 1px solid var(--card-content-divider);
}
-.settingContent .form {
- display: flex;
- flex-direction: row;
- align-items: center;
- gap: 12px;
-}
-
-.settingContent input[type='text'] {
- flex: 1;
- background-color: var(--settings-input-background-color);
- border: 1px solid var(--settings-input-border-color);
- border-radius: 50px;
- padding: 6px 18px;
- color: var(--settings-input-text-color);
- font-size: 14px;
-}
-
-.settingContent input[type='text']::placeholder {
- /* Chrome, Firefox, Opera, Safari 10.1+ */
- color: var(--settings-input-placeholder-color);
- opacity: 1;
- font-size: 14px;
-}
-
-.settingContent input[type='text']:focus {
- border-color: var(--settings-input-border-focus-color);
-}
-
.settingContent button {
display: flex;
align-items: center;
@@ -92,10 +35,12 @@ Modal
.rssButton {
background-color: #ee802f;
color: white;
+ &:hover {
+ background-color: #f99147;
+ color: white;
+ }
}
-.rssButton:hover {
- opacity: 0.9;
-}
+
.settingContent {
width: 100%;
flex: 1;
@@ -110,37 +55,6 @@ Modal
font-weight: 500;
color: var(--primary-text-color);
}
-.modalHeader {
- display: flex;
- flex-direction: row;
- justify-content: space-between;
- margin-bottom: 16px;
-}
-
-.modalTitle {
- margin: 0;
- padding: 0;
- color: var(--primary-text-color);
-}
-.modalCloseBtn {
- align-items: center;
- background-color: transparent;
- border-radius: 50%;
- border: none;
- color: var(--primary-text-color);
- cursor: pointer;
- display: flex;
- height: 40px;
- justify-content: center;
- margin: 0;
- padding: 0;
- text-align: center;
- width: 40px;
-}
-.modalCloseBtn:hover {
- opacity: 0.7;
-}
-
/**
Select styles
**/
@@ -182,24 +96,36 @@ Select styles
color: var(--tag-secondary-color) !important;
}
-@media (max-width: 768px) {
- .Modal {
- left: 0;
- top: 0;
- margin: 0;
- height: 100vh;
- max-height: 100vh;
- transform: translate(0, 0);
- border-radius: 0;
- position: relative;
- box-shadow: none;
- width: auto;
- }
- .settingContent {
- margin-top: 6px;
- }
- .settingRow {
- flex-direction: column;
- align-items: flex-start;
- }
+.userContent {
+ width: auto;
+ padding: 20px 0px;
+ display: flex;
+ align-items: center;
+ border-bottom: 1px solid var(--card-content-divider);
+}
+.userContent .userImage {
+ width: 50px;
+ height: 50px;
+ border-radius: 100%;
+ align-self: flex-start;
+}
+.userContent .userInfos {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ width: auto;
+ margin-left: 10px;
+}
+.userContent .userName {
+ font-weight: 600;
+}
+
+.userContent .sub {
+ font-size: 0.9em;
+ opacity: 0.9;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ column-gap: 4px;
}
diff --git a/src/features/settings/components/RssSetting.tsx b/src/features/settings/components/RssSetting.tsx
index 54be6d6f..daefce43 100644
--- a/src/features/settings/components/RssSetting.tsx
+++ b/src/features/settings/components/RssSetting.tsx
@@ -5,6 +5,7 @@ import { identifyUserCards, trackRssSourceAdd } from 'src/lib/analytics'
import { isValidURL } from 'src/utils/UrlUtils'
import { BsRssFill } from 'react-icons/bs'
+import { Button } from 'src/components/Elements'
import { useUserPreferences } from 'src/stores/preferences'
import { SupportedCardType } from 'src/types'
@@ -78,9 +79,13 @@ export const RssSetting = () => {
) : (
-
- Add
-
+ }
+ size="small"
+ className="rssButton"
+ onClick={onRssAddClick}>
+ Add
+
)}
diff --git a/src/features/settings/index.ts b/src/features/settings/index.ts
index 0e607d08..74f1de9f 100644
--- a/src/features/settings/index.ts
+++ b/src/features/settings/index.ts
@@ -1,5 +1,6 @@
export * from './components/BookmarkSettings'
export * from './components/GeneralSettings'
+
export * from './components/SearchEngineSettings'
export * from './components/SourceSettings'
export * from './components/TopicSettings'
diff --git a/src/features/shareModal/components/ShareModal.tsx b/src/features/shareModal/components/ShareModal.tsx
index cab95c4c..103e5bd1 100644
--- a/src/features/shareModal/components/ShareModal.tsx
+++ b/src/features/shareModal/components/ShareModal.tsx
@@ -17,6 +17,7 @@ import {
WhatsappShareButton,
} from 'react-share'
import toast from 'react-simple-toasts'
+import { Button } from 'src/components/Elements'
import { twitterHandle } from 'src/config'
import { trackLinkCopy, trackLinkShare } from 'src/lib/analytics'
import { ShareModalData } from '../types'
@@ -118,9 +119,14 @@ export const ShareModal = ({ showModal, closeModal, shareData }: ShareModalProps
diff --git a/src/lib/analytics.ts b/src/lib/analytics.ts
index c03674e1..6462dead 100644
--- a/src/lib/analytics.ts
+++ b/src/lib/analytics.ts
@@ -44,6 +44,8 @@ enum Verbs {
DISABLE = 'Disable',
SHARE = 'Share',
COPY = 'Copy',
+ CONNECT = 'Connect',
+ DISCONNECT = 'Disconnect',
}
export enum Attributes {
@@ -376,6 +378,21 @@ export const trackLinkCopy = ({
})
}
+export const trackUserConnect = (provider: string) => {
+ trackEvent({
+ object: Objects.USER,
+ verb: Verbs.CONNECT,
+ attributes: { [Attributes.PROVIDER]: provider },
+ })
+}
+
+export const trackUserDisconnect = () => {
+ trackEvent({
+ object: Objects.USER,
+ verb: Verbs.DISCONNECT,
+ })
+}
+
// Identification
export const identifyUserLanguages = (languages: string[]) => {
diff --git a/src/lib/firebase.ts b/src/lib/firebase.ts
new file mode 100644
index 00000000..8417befc
--- /dev/null
+++ b/src/lib/firebase.ts
@@ -0,0 +1,15 @@
+import { initializeApp } from 'firebase/app'
+import { getAuth } from 'firebase/auth'
+import { FIREBASE_API_KEY } from 'src/config'
+
+const firebaseConfig = {
+ apiKey: FIREBASE_API_KEY,
+}
+
+if (!FIREBASE_API_KEY) {
+ console.warn('Missing Firebase api Key')
+}
+// Initialize Firebase
+const app = initializeApp(firebaseConfig)
+const firebaseAuth = getAuth(app)
+export { firebaseAuth }
diff --git a/src/lib/interceptors/DefaultRequestInterceptor.ts b/src/lib/interceptors/DefaultRequestInterceptor.ts
index be883e3c..3cd9de14 100644
--- a/src/lib/interceptors/DefaultRequestInterceptor.ts
+++ b/src/lib/interceptors/DefaultRequestInterceptor.ts
@@ -1,13 +1,20 @@
import { AxiosRequestConfig } from 'axios'
import { API_ENDPOINT } from 'src/config'
import { isProduction } from 'src/utils/Environment'
+import { firebaseAuth } from '../firebase'
-export function DefaultRequestInterceptor(config: AxiosRequestConfig) {
+export async function DefaultRequestInterceptor(config: AxiosRequestConfig) {
if (config) {
config.baseURL = isProduction() ? API_ENDPOINT : '/api'
if (config.headers) {
config.headers.Accept = 'application/json'
}
+
+ const user = firebaseAuth.currentUser
+ if (user) {
+ const token = await user.getIdToken()
+ config.headers.Authorization = `Bearer ${token}`
+ }
}
return config
diff --git a/src/providers/AuthProvider.tsx b/src/providers/AuthProvider.tsx
new file mode 100644
index 00000000..469b6c3b
--- /dev/null
+++ b/src/providers/AuthProvider.tsx
@@ -0,0 +1,95 @@
+import { GithubAuthProvider, GoogleAuthProvider, signInWithCredential } from 'firebase/auth'
+import { useCallback, useEffect } from 'react'
+import { useNavigate, useSearchParams } from 'react-router-dom'
+import toast from 'react-simple-toasts'
+import { useAuth } from 'src/features/auth'
+import { firebaseAuth } from 'src/lib/firebase'
+
+export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
+ const navigate = useNavigate()
+ const [searchParams] = useSearchParams()
+ const { closeAuthModal, initState, setAuthError } = useAuth()
+
+ const connectTheUser = useCallback((token?: string | null, provider?: string | null) => {
+ const allowedProviders = ['google', 'github']
+ if ((provider && !allowedProviders.includes(provider)) || !token) {
+ return Promise.resolve()
+ }
+
+ const authProvider =
+ provider === 'google'
+ ? GoogleAuthProvider.credential(null, token)
+ : GithubAuthProvider.credential(token)
+
+ return signInWithCredential(firebaseAuth, authProvider).then((userCredential) => {
+ const user = userCredential.user
+
+ initState({
+ user: {
+ name: user.displayName || 'Anonymous',
+ imageURL: user.photoURL || '',
+ },
+ providerId: authProvider.providerId,
+ })
+ if (user.displayName) {
+ toast(`Welcome, ${user.displayName}`, { theme: 'successToast' })
+ }
+ closeAuthModal()
+ navigate(window.location.pathname, { replace: true })
+ })
+ }, [])
+
+ /**
+ * This effect is used to connect the user when the token is received from the background script
+ * on Chrome and Firefox extensions
+ */
+ useEffect(() => {
+ const messageListener = (message: {
+ action: string
+ type?: string
+ access_token?: string
+ provider?: string
+ }) => {
+ if (message.type === 'TOKEN_RECEIVED') {
+ const { access_token: token, provider } = message
+ connectTheUser(token, provider).catch((error) => {
+ if (error && error.code === 'auth/account-exists-with-different-credential') {
+ setAuthError({
+ message:
+ 'You have an account with a different provider. Please sign in with that provider to continue.',
+ })
+ }
+ })
+ }
+ }
+
+ chrome.runtime?.onMessage.addListener(messageListener)
+
+ return () => {
+ chrome.runtime?.onMessage.removeListener(messageListener)
+ }
+ }, [])
+
+ /**
+ * This effect is used to connect the user when the user when the token is received from the URL
+ * on the web
+ */
+ useEffect(() => {
+ const token = searchParams.get('access_token')
+ const provider = searchParams.get('provider')
+ connectTheUser(token, provider).catch((error) => {
+ if (error && error.code === 'auth/account-exists-with-different-credential') {
+ toast(
+ 'You have an account with a different provider. Please sign in with that provider to continue.',
+ {
+ theme: 'dangerToast',
+ }
+ )
+ } else {
+ console.log(error)
+ toast('Error signing in, Please try again', { theme: 'dangerToast' })
+ }
+ })
+ }, [searchParams])
+ return <>{children}>
+}
diff --git a/src/stores/bookmarks.ts b/src/stores/bookmarks.ts
index d99827b0..71f63f5c 100644
--- a/src/stores/bookmarks.ts
+++ b/src/stores/bookmarks.ts
@@ -1,24 +1,37 @@
-import { create } from 'zustand';
+import { create } from 'zustand'
-import { BookmarkedPost } from "src/features/bookmarks";
-import { persist } from 'zustand/middleware';
+import { BookmarkedPost } from 'src/features/bookmarks'
+import { persist } from 'zustand/middleware'
type BookmarksState = {
userBookmarks: BookmarkedPost[]
-};
+}
type BookmarksActions = {
- bookmarkPost: (post: BookmarkedPost) => void;
- unbookmarkPost: (post: BookmarkedPost) => void;
- initState: (state: BookmarksState) => void;
+ bookmarkPost: (post: BookmarkedPost) => void
+ unbookmarkPost: (post: BookmarkedPost) => void
+ initState: (state: BookmarksState) => void
+ clear: () => void
}
-
-export const useBookmarks = create(persist
((set) => ({
- userBookmarks: [],
- bookmarkPost: (post: BookmarkedPost) => set((state) => ({ userBookmarks: [post, ...state.userBookmarks] })),
- unbookmarkPost: (post: BookmarkedPost) => set((state) => ({ userBookmarks: state.userBookmarks.filter((bookmarkedPost) => bookmarkedPost.url !== post.url), })),
- initState: (newState: BookmarksState) => set(() => ({ userBookmarks: newState.userBookmarks }))
-}), {
- name: 'bookmarks_storage'
-}));
+export const useBookmarks = create(
+ persist(
+ (set) => ({
+ userBookmarks: [],
+ bookmarkPost: (post: BookmarkedPost) =>
+ set((state) => ({ userBookmarks: [post, ...state.userBookmarks] })),
+ unbookmarkPost: (post: BookmarkedPost) =>
+ set((state) => ({
+ userBookmarks: state.userBookmarks.filter(
+ (bookmarkedPost) => bookmarkedPost.url !== post.url
+ ),
+ })),
+ initState: (newState: BookmarksState) =>
+ set(() => ({ userBookmarks: newState.userBookmarks })),
+ clear: () => set({ userBookmarks: [] }),
+ }),
+ {
+ name: 'bookmarks_storage',
+ }
+ )
+)
diff --git a/src/utils/Environment.ts b/src/utils/Environment.ts
index a80a14ef..1a2f781c 100644
--- a/src/utils/Environment.ts
+++ b/src/utils/Environment.ts
@@ -6,12 +6,12 @@ export const isDevelopment = (): boolean => {
return import.meta.env.DEV
}
-export const isWebOrExtensionVersion = (): string => {
+export const isWebOrExtensionVersion = (): 'web' | 'extension' => {
const buildTarget = import.meta.env.VITE_BUILD_TARGET as 'web' | 'extension' | undefined
return buildTarget || 'web'
}
-export const getBrowserName = (): string => {
+export const getBrowserName = (): 'chrome' | 'firefox' | 'other' => {
let userAgent = navigator.userAgent
if (userAgent.match(/chrome|chromium|crios/i)) {
return 'chrome'
diff --git a/src/utils/Permissions.ts b/src/utils/Permissions.ts
new file mode 100644
index 00000000..1390ee21
--- /dev/null
+++ b/src/utils/Permissions.ts
@@ -0,0 +1,28 @@
+import { getBrowserName } from './Environment'
+
+// Check host permissions before allowing the user to sign in with a provider.
+// as Firefox may block the Signin on v3 if the extension does not have host permissions.
+export const checkHostPermissions = async () => {
+ if (getBrowserName() !== 'firefox') {
+ return true
+ }
+
+ const HOST_PERMISSIONS = chrome.runtime.getManifest().content_scripts || []
+ const requiredHosts = HOST_PERMISSIONS.flatMap((permission) => {
+ return permission.matches || []
+ })
+
+ return await chrome.permissions.contains({ origins: requiredHosts })
+}
+
+export const requestHostPermissions = async () => {
+ if (getBrowserName() !== 'firefox') {
+ return true
+ }
+ const HOST_PERMISSIONS = chrome.runtime.getManifest().content_scripts || []
+ const requiredHosts = HOST_PERMISSIONS.flatMap((permission) => {
+ return permission.matches || []
+ })
+
+ return await chrome.permissions.request({ origins: requiredHosts })
+}
diff --git a/yarn.lock b/yarn.lock
index b38ede3b..62c049f7 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1498,6 +1498,396 @@
resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.10.0.tgz#548f6de556857c8bb73bbee70c35dc82a2e74d63"
integrity sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==
+"@firebase/analytics-compat@0.2.17":
+ version "0.2.17"
+ resolved "https://registry.yarnpkg.com/@firebase/analytics-compat/-/analytics-compat-0.2.17.tgz#c3cfc8ffb863d574ec26d86f9c8344d752832995"
+ integrity sha512-SJNVOeTvzdqZQvXFzj7yAirXnYcLDxh57wBFROfeowq/kRN1AqOw1tG6U4OiFOEhqi7s3xLze/LMkZatk2IEww==
+ dependencies:
+ "@firebase/analytics" "0.10.11"
+ "@firebase/analytics-types" "0.8.3"
+ "@firebase/component" "0.6.12"
+ "@firebase/util" "1.10.3"
+ tslib "^2.1.0"
+
+"@firebase/analytics-types@0.8.3":
+ version "0.8.3"
+ resolved "https://registry.yarnpkg.com/@firebase/analytics-types/-/analytics-types-0.8.3.tgz#d08cd39a6209693ca2039ba7a81570dfa6c1518f"
+ integrity sha512-VrIp/d8iq2g501qO46uGz3hjbDb8xzYMrbu8Tp0ovzIzrvJZ2fvmj649gTjge/b7cCCcjT0H37g1gVtlNhnkbg==
+
+"@firebase/analytics@0.10.11":
+ version "0.10.11"
+ resolved "https://registry.yarnpkg.com/@firebase/analytics/-/analytics-0.10.11.tgz#6896413e92613573af775c45050af889a43676da"
+ integrity sha512-zwuPiRE0+hgcS95JZbJ6DFQN4xYFO8IyGxpeePTV51YJMwCf3lkBa6FnZ/iXIqDKcBPMgMuuEZozI0BJWaLEYg==
+ dependencies:
+ "@firebase/component" "0.6.12"
+ "@firebase/installations" "0.6.12"
+ "@firebase/logger" "0.4.4"
+ "@firebase/util" "1.10.3"
+ tslib "^2.1.0"
+
+"@firebase/app-check-compat@0.3.18":
+ version "0.3.18"
+ resolved "https://registry.yarnpkg.com/@firebase/app-check-compat/-/app-check-compat-0.3.18.tgz#abe63858fca86b61ea431e0d9e58ccb8bac1b275"
+ integrity sha512-qjozwnwYmAIdrsVGrJk+hnF1WBois54IhZR6gO0wtZQoTvWL/GtiA2F31TIgAhF0ayUiZhztOv1RfC7YyrZGDQ==
+ dependencies:
+ "@firebase/app-check" "0.8.11"
+ "@firebase/app-check-types" "0.5.3"
+ "@firebase/component" "0.6.12"
+ "@firebase/logger" "0.4.4"
+ "@firebase/util" "1.10.3"
+ tslib "^2.1.0"
+
+"@firebase/app-check-interop-types@0.3.3":
+ version "0.3.3"
+ resolved "https://registry.yarnpkg.com/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.3.tgz#ed9c4a4f48d1395ef378f007476db3940aa5351a"
+ integrity sha512-gAlxfPLT2j8bTI/qfe3ahl2I2YcBQ8cFIBdhAQA4I2f3TndcO+22YizyGYuttLHPQEpWkhmpFW60VCFEPg4g5A==
+
+"@firebase/app-check-types@0.5.3":
+ version "0.5.3"
+ resolved "https://registry.yarnpkg.com/@firebase/app-check-types/-/app-check-types-0.5.3.tgz#38ba954acf4bffe451581a32fffa20337f11d8e5"
+ integrity sha512-hyl5rKSj0QmwPdsAxrI5x1otDlByQ7bvNvVt8G/XPO2CSwE++rmSVf3VEhaeOR4J8ZFaF0Z0NDSmLejPweZ3ng==
+
+"@firebase/app-check@0.8.11":
+ version "0.8.11"
+ resolved "https://registry.yarnpkg.com/@firebase/app-check/-/app-check-0.8.11.tgz#3c67148046fea0a0a9a1eecf1a17fdc31a76eda7"
+ integrity sha512-42zIfRI08/7bQqczAy7sY2JqZYEv3a1eNa4fLFdtJ54vNevbBIRSEA3fZgRqWFNHalh5ohsBXdrYgFqaRIuCcQ==
+ dependencies:
+ "@firebase/component" "0.6.12"
+ "@firebase/logger" "0.4.4"
+ "@firebase/util" "1.10.3"
+ tslib "^2.1.0"
+
+"@firebase/app-compat@0.2.48":
+ version "0.2.48"
+ resolved "https://registry.yarnpkg.com/@firebase/app-compat/-/app-compat-0.2.48.tgz#4cc013dc53b12c9c2ebda6369bbeb99f3cc59975"
+ integrity sha512-wVNU1foBIaJncUmiALyRxhHHHC3ZPMLIETTAk+2PG87eP9B/IDBsYUiTpHyboDPEI8CgBPat/zN2v+Snkz6lBw==
+ dependencies:
+ "@firebase/app" "0.10.18"
+ "@firebase/component" "0.6.12"
+ "@firebase/logger" "0.4.4"
+ "@firebase/util" "1.10.3"
+ tslib "^2.1.0"
+
+"@firebase/app-types@0.9.3":
+ version "0.9.3"
+ resolved "https://registry.yarnpkg.com/@firebase/app-types/-/app-types-0.9.3.tgz#8408219eae9b1fb74f86c24e7150a148460414ad"
+ integrity sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw==
+
+"@firebase/app@0.10.18":
+ version "0.10.18"
+ resolved "https://registry.yarnpkg.com/@firebase/app/-/app-0.10.18.tgz#219d897beedcc833ab6d7bdc4ea810ece9e32df6"
+ integrity sha512-VuqEwD/QRisKd/zsFsqgvSAx34mZ3WEF47i97FD6Vw4GWAhdjepYf0Hmi6K0b4QMSgWcv/x0C30Slm5NjjERXg==
+ dependencies:
+ "@firebase/component" "0.6.12"
+ "@firebase/logger" "0.4.4"
+ "@firebase/util" "1.10.3"
+ idb "7.1.1"
+ tslib "^2.1.0"
+
+"@firebase/auth-compat@0.5.17":
+ version "0.5.17"
+ resolved "https://registry.yarnpkg.com/@firebase/auth-compat/-/auth-compat-0.5.17.tgz#60d9222dae734fb8740ac565e342dc11f92d2392"
+ integrity sha512-Shi6rqLqzU9KLXnUCmlLvVByq1kiG3oe7Wpbf5m1CgS7NiRx2pSSn0HLaRRozdkaizNzMGGj+3oHmNYQ7kU6xA==
+ dependencies:
+ "@firebase/auth" "1.8.2"
+ "@firebase/auth-types" "0.12.3"
+ "@firebase/component" "0.6.12"
+ "@firebase/util" "1.10.3"
+ tslib "^2.1.0"
+
+"@firebase/auth-interop-types@0.2.4":
+ version "0.2.4"
+ resolved "https://registry.yarnpkg.com/@firebase/auth-interop-types/-/auth-interop-types-0.2.4.tgz#176a08686b0685596ff03d7879b7e4115af53de0"
+ integrity sha512-JPgcXKCuO+CWqGDnigBtvo09HeBs5u/Ktc2GaFj2m01hLarbxthLNm7Fk8iOP1aqAtXV+fnnGj7U28xmk7IwVA==
+
+"@firebase/auth-types@0.12.3":
+ version "0.12.3"
+ resolved "https://registry.yarnpkg.com/@firebase/auth-types/-/auth-types-0.12.3.tgz#650e54a36060b5ea012075ddbd3cb26566334c41"
+ integrity sha512-Zq9zI0o5hqXDtKg6yDkSnvMCMuLU6qAVS51PANQx+ZZX5xnzyNLEBO3GZgBUPsV5qIMFhjhqmLDxUqCbnAYy2A==
+
+"@firebase/auth@1.8.2":
+ version "1.8.2"
+ resolved "https://registry.yarnpkg.com/@firebase/auth/-/auth-1.8.2.tgz#4559dfabe40bc7a0605fb2a73f401d83cb32fe80"
+ integrity sha512-q+071y2LWe0bVnjqaX3BscqZwzdP0GKN2YBKapLq4bV88MPfCtWwGKmDhNDEDUmioOjudGXkUY5cvvKqk3mlUg==
+ dependencies:
+ "@firebase/component" "0.6.12"
+ "@firebase/logger" "0.4.4"
+ "@firebase/util" "1.10.3"
+ tslib "^2.1.0"
+
+"@firebase/component@0.6.12":
+ version "0.6.12"
+ resolved "https://registry.yarnpkg.com/@firebase/component/-/component-0.6.12.tgz#08905a534e9b769164e7e1b1e80f6e7611eb67f3"
+ integrity sha512-YnxqjtohLbnb7raXt2YuA44cC1wA9GiehM/cmxrsoxKlFxBLy2V0OkRSj9gpngAE0UoJ421Wlav9ycO7lTPAUw==
+ dependencies:
+ "@firebase/util" "1.10.3"
+ tslib "^2.1.0"
+
+"@firebase/data-connect@0.2.0":
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/@firebase/data-connect/-/data-connect-0.2.0.tgz#7133cb40096466dc17ad01a50a5f2c383605df20"
+ integrity sha512-7OrZtQoLSk2fiGijhIdUnTSqEFti3h1EMhw9nNiSZ6jJGduw4Pz6jrVvxjpZJtGH/JiljbMkBnPBS2h8CTRKEw==
+ dependencies:
+ "@firebase/auth-interop-types" "0.2.4"
+ "@firebase/component" "0.6.12"
+ "@firebase/logger" "0.4.4"
+ "@firebase/util" "1.10.3"
+ tslib "^2.1.0"
+
+"@firebase/database-compat@2.0.2":
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/@firebase/database-compat/-/database-compat-2.0.2.tgz#9ddf474b593766a41ea576185cdf115e28f0cb50"
+ integrity sha512-5zvdnMsfDHvrQAVM6jBS7CkBpu+z3YbpFdhxRsrK1FP45IEfxlzpeuEUb17D/tpM10vfq4Ok0x5akIBaCv7gfA==
+ dependencies:
+ "@firebase/component" "0.6.12"
+ "@firebase/database" "1.0.11"
+ "@firebase/database-types" "1.0.8"
+ "@firebase/logger" "0.4.4"
+ "@firebase/util" "1.10.3"
+ tslib "^2.1.0"
+
+"@firebase/database-types@1.0.8":
+ version "1.0.8"
+ resolved "https://registry.yarnpkg.com/@firebase/database-types/-/database-types-1.0.8.tgz#eddcce594be118bf9aebb043b5a6d51cfb6de620"
+ integrity sha512-6lPWIGeufhUq1heofZULyVvWFhD01TUrkkB9vyhmksjZ4XF7NaivQp9rICMk7QNhqwa+uDCaj4j+Q8qqcSVZ9g==
+ dependencies:
+ "@firebase/app-types" "0.9.3"
+ "@firebase/util" "1.10.3"
+
+"@firebase/database@1.0.11":
+ version "1.0.11"
+ resolved "https://registry.yarnpkg.com/@firebase/database/-/database-1.0.11.tgz#5b9960a07a0d49361f67fc69affc779cae4e07e3"
+ integrity sha512-gLrw/XeioswWUXgpVKCPAzzoOuvYNqK5fRUeiJTzO7Mlp9P6ylFEyPJlRBl1djqYye641r3MX6AmIeMXwjgwuQ==
+ dependencies:
+ "@firebase/app-check-interop-types" "0.3.3"
+ "@firebase/auth-interop-types" "0.2.4"
+ "@firebase/component" "0.6.12"
+ "@firebase/logger" "0.4.4"
+ "@firebase/util" "1.10.3"
+ faye-websocket "0.11.4"
+ tslib "^2.1.0"
+
+"@firebase/firestore-compat@0.3.41":
+ version "0.3.41"
+ resolved "https://registry.yarnpkg.com/@firebase/firestore-compat/-/firestore-compat-0.3.41.tgz#434d86fd603b5ebcde19b5695b9b2a53bb23be8b"
+ integrity sha512-J/PgWKEt0yugETOE7lOabT16hsV21cLzSxERD7ZhaiwBQkBTSf0Mx9RhjZRT0Ttqe4weM90HGZFyUBqYA73fVA==
+ dependencies:
+ "@firebase/component" "0.6.12"
+ "@firebase/firestore" "4.7.6"
+ "@firebase/firestore-types" "3.0.3"
+ "@firebase/util" "1.10.3"
+ tslib "^2.1.0"
+
+"@firebase/firestore-types@3.0.3":
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/@firebase/firestore-types/-/firestore-types-3.0.3.tgz#7d0c3dd8850c0193d8f5ee0cc8f11961407742c1"
+ integrity sha512-hD2jGdiWRxB/eZWF89xcK9gF8wvENDJkzpVFb4aGkzfEaKxVRD1kjz1t1Wj8VZEp2LCB53Yx1zD8mrhQu87R6Q==
+
+"@firebase/firestore@4.7.6":
+ version "4.7.6"
+ resolved "https://registry.yarnpkg.com/@firebase/firestore/-/firestore-4.7.6.tgz#877a37b615d86c61ac9ba7ddd0a967feb99380ac"
+ integrity sha512-aVDboR+upR/44qZDLR4tnZ9pepSOFBbDJnwk7eWzmTyQq2nZAVG+HIhrqpQawmUVcDRkuJv2K2UT2+oqR8F8TA==
+ dependencies:
+ "@firebase/component" "0.6.12"
+ "@firebase/logger" "0.4.4"
+ "@firebase/util" "1.10.3"
+ "@firebase/webchannel-wrapper" "1.0.3"
+ "@grpc/grpc-js" "~1.9.0"
+ "@grpc/proto-loader" "^0.7.8"
+ tslib "^2.1.0"
+
+"@firebase/functions-compat@0.3.18":
+ version "0.3.18"
+ resolved "https://registry.yarnpkg.com/@firebase/functions-compat/-/functions-compat-0.3.18.tgz#c6a4f6b0762c6990db0aab244420c3e1240fda2b"
+ integrity sha512-N7+RN5GVus2ORB8cqfSNhfSn4iaYws6F8uCCfn4mtjC7zYS/KH6muzNAhZUdUqlv5YazbVmvxlAoYYF39i8Qzg==
+ dependencies:
+ "@firebase/component" "0.6.12"
+ "@firebase/functions" "0.12.1"
+ "@firebase/functions-types" "0.6.3"
+ "@firebase/util" "1.10.3"
+ tslib "^2.1.0"
+
+"@firebase/functions-types@0.6.3":
+ version "0.6.3"
+ resolved "https://registry.yarnpkg.com/@firebase/functions-types/-/functions-types-0.6.3.tgz#f5faf770248b13f45d256f614230da6a11bfb654"
+ integrity sha512-EZoDKQLUHFKNx6VLipQwrSMh01A1SaL3Wg6Hpi//x6/fJ6Ee4hrAeswK99I5Ht8roiniKHw4iO0B1Oxj5I4plg==
+
+"@firebase/functions@0.12.1":
+ version "0.12.1"
+ resolved "https://registry.yarnpkg.com/@firebase/functions/-/functions-0.12.1.tgz#09ca7386619b0c50c535f6695e5be0712d3e4730"
+ integrity sha512-QucRiFrvMMmIGTRhL7ZK2IeBnAWP7lAmfFREMpEtX47GjVqDqGxdFs+Mg7XBzxSc9UjDO4Rxf+aE9xJHU6bGwg==
+ dependencies:
+ "@firebase/app-check-interop-types" "0.3.3"
+ "@firebase/auth-interop-types" "0.2.4"
+ "@firebase/component" "0.6.12"
+ "@firebase/messaging-interop-types" "0.2.3"
+ "@firebase/util" "1.10.3"
+ tslib "^2.1.0"
+
+"@firebase/installations-compat@0.2.12":
+ version "0.2.12"
+ resolved "https://registry.yarnpkg.com/@firebase/installations-compat/-/installations-compat-0.2.12.tgz#ee6396f3cc787c0dd4fc5dd87fec1db9dbb40c97"
+ integrity sha512-RhcGknkxmFu92F6Jb3rXxv6a4sytPjJGifRZj8MSURPuv2Xu+/AispCXEfY1ZraobhEHTG5HLGsP6R4l9qB5aA==
+ dependencies:
+ "@firebase/component" "0.6.12"
+ "@firebase/installations" "0.6.12"
+ "@firebase/installations-types" "0.5.3"
+ "@firebase/util" "1.10.3"
+ tslib "^2.1.0"
+
+"@firebase/installations-types@0.5.3":
+ version "0.5.3"
+ resolved "https://registry.yarnpkg.com/@firebase/installations-types/-/installations-types-0.5.3.tgz#cac8a14dd49f09174da9df8ae453f9b359c3ef2f"
+ integrity sha512-2FJI7gkLqIE0iYsNQ1P751lO3hER+Umykel+TkLwHj6plzWVxqvfclPUZhcKFVQObqloEBTmpi2Ozn7EkCABAA==
+
+"@firebase/installations@0.6.12":
+ version "0.6.12"
+ resolved "https://registry.yarnpkg.com/@firebase/installations/-/installations-0.6.12.tgz#6d9ad14e60caa8fae4ec0120c0e46ceb9d6fbdae"
+ integrity sha512-ES/WpuAV2k2YtBTvdaknEo7IY8vaGjIjS3zhnHSAIvY9KwTR8XZFXOJoZ3nSkjN1A5R4MtEh+07drnzPDg9vaw==
+ dependencies:
+ "@firebase/component" "0.6.12"
+ "@firebase/util" "1.10.3"
+ idb "7.1.1"
+ tslib "^2.1.0"
+
+"@firebase/logger@0.4.4":
+ version "0.4.4"
+ resolved "https://registry.yarnpkg.com/@firebase/logger/-/logger-0.4.4.tgz#29e8379d20fd1149349a195ee6deee4573a86f48"
+ integrity sha512-mH0PEh1zoXGnaR8gD1DeGeNZtWFKbnz9hDO91dIml3iou1gpOnLqXQ2dJfB71dj6dpmUjcQ6phY3ZZJbjErr9g==
+ dependencies:
+ tslib "^2.1.0"
+
+"@firebase/messaging-compat@0.2.16":
+ version "0.2.16"
+ resolved "https://registry.yarnpkg.com/@firebase/messaging-compat/-/messaging-compat-0.2.16.tgz#533af4542a54b932146d175d5687aedd428be972"
+ integrity sha512-9HZZ88Ig3zQ0ok/Pwt4gQcNsOhoEy8hDHoGsV1am6ulgMuGuDVD2gl11Lere2ksL+msM12Lddi2x/7TCqmODZw==
+ dependencies:
+ "@firebase/component" "0.6.12"
+ "@firebase/messaging" "0.12.16"
+ "@firebase/util" "1.10.3"
+ tslib "^2.1.0"
+
+"@firebase/messaging-interop-types@0.2.3":
+ version "0.2.3"
+ resolved "https://registry.yarnpkg.com/@firebase/messaging-interop-types/-/messaging-interop-types-0.2.3.tgz#e647c9cd1beecfe6a6e82018a6eec37555e4da3e"
+ integrity sha512-xfzFaJpzcmtDjycpDeCUj0Ge10ATFi/VHVIvEEjDNc3hodVBQADZ7BWQU7CuFpjSHE+eLuBI13z5F/9xOoGX8Q==
+
+"@firebase/messaging@0.12.16":
+ version "0.12.16"
+ resolved "https://registry.yarnpkg.com/@firebase/messaging/-/messaging-0.12.16.tgz#bd8a768274bdc4368396bd9eaa356bffb998bef2"
+ integrity sha512-VJ8sCEIeP3+XkfbJA7410WhYGHdloYFZXoHe/vt+vNVDGw8JQPTQSVTRvjrUprEf5I4Tbcnpr2H34lS6zhCHSA==
+ dependencies:
+ "@firebase/component" "0.6.12"
+ "@firebase/installations" "0.6.12"
+ "@firebase/messaging-interop-types" "0.2.3"
+ "@firebase/util" "1.10.3"
+ idb "7.1.1"
+ tslib "^2.1.0"
+
+"@firebase/performance-compat@0.2.12":
+ version "0.2.12"
+ resolved "https://registry.yarnpkg.com/@firebase/performance-compat/-/performance-compat-0.2.12.tgz#069284005e3f29339b570ee517b813d9bdbc0a89"
+ integrity sha512-DyCbDTIwtBTGsEiQxTz/TD23a0na2nrDozceQ5kVkszyFYvliB0YK/9el0wAGIG91SqgTG9pxHtYErzfZc0VWw==
+ dependencies:
+ "@firebase/component" "0.6.12"
+ "@firebase/logger" "0.4.4"
+ "@firebase/performance" "0.6.12"
+ "@firebase/performance-types" "0.2.3"
+ "@firebase/util" "1.10.3"
+ tslib "^2.1.0"
+
+"@firebase/performance-types@0.2.3":
+ version "0.2.3"
+ resolved "https://registry.yarnpkg.com/@firebase/performance-types/-/performance-types-0.2.3.tgz#5ce64e90fa20ab5561f8b62a305010cf9fab86fb"
+ integrity sha512-IgkyTz6QZVPAq8GSkLYJvwSLr3LS9+V6vNPQr0x4YozZJiLF5jYixj0amDtATf1X0EtYHqoPO48a9ija8GocxQ==
+
+"@firebase/performance@0.6.12":
+ version "0.6.12"
+ resolved "https://registry.yarnpkg.com/@firebase/performance/-/performance-0.6.12.tgz#58763cbbe31673351e1494875a5c10c6b68e1322"
+ integrity sha512-8mYL4z2jRlKXAi2hjk4G7o2sQLnJCCuTbyvti/xmHf5ZvOIGB01BZec0aDuBIXO+H1MLF62dbye/k91Fr+yc8g==
+ dependencies:
+ "@firebase/component" "0.6.12"
+ "@firebase/installations" "0.6.12"
+ "@firebase/logger" "0.4.4"
+ "@firebase/util" "1.10.3"
+ tslib "^2.1.0"
+
+"@firebase/remote-config-compat@0.2.12":
+ version "0.2.12"
+ resolved "https://registry.yarnpkg.com/@firebase/remote-config-compat/-/remote-config-compat-0.2.12.tgz#ae0b597b3228deef0e3c6b2c6e631f19213eca4c"
+ integrity sha512-91jLWPtubIuPBngg9SzwvNCWzhMLcyBccmt7TNZP+y1cuYFNOWWHKUXQ3IrxCLB7WwLqQaEu7fTDAjHsTyBsSw==
+ dependencies:
+ "@firebase/component" "0.6.12"
+ "@firebase/logger" "0.4.4"
+ "@firebase/remote-config" "0.5.0"
+ "@firebase/remote-config-types" "0.4.0"
+ "@firebase/util" "1.10.3"
+ tslib "^2.1.0"
+
+"@firebase/remote-config-types@0.4.0":
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/@firebase/remote-config-types/-/remote-config-types-0.4.0.tgz#91b9a836d5ca30ced68c1516163b281fbb544537"
+ integrity sha512-7p3mRE/ldCNYt8fmWMQ/MSGRmXYlJ15Rvs9Rk17t8p0WwZDbeK7eRmoI1tvCPaDzn9Oqh+yD6Lw+sGLsLg4kKg==
+
+"@firebase/remote-config@0.5.0":
+ version "0.5.0"
+ resolved "https://registry.yarnpkg.com/@firebase/remote-config/-/remote-config-0.5.0.tgz#30212fa77adba8a62fc6408eb32122147ae80790"
+ integrity sha512-weiEbpBp5PBJTHUWR4GwI7ZacaAg68BKha5QnZ8Go65W4oQjEWqCW/rfskABI/OkrGijlL3CUmCB/SA6mVo0qA==
+ dependencies:
+ "@firebase/component" "0.6.12"
+ "@firebase/installations" "0.6.12"
+ "@firebase/logger" "0.4.4"
+ "@firebase/util" "1.10.3"
+ tslib "^2.1.0"
+
+"@firebase/storage-compat@0.3.15":
+ version "0.3.15"
+ resolved "https://registry.yarnpkg.com/@firebase/storage-compat/-/storage-compat-0.3.15.tgz#02a18e0e1866849206dba7b075a4dcd99489aae7"
+ integrity sha512-Z9afjrK2O9o1ZHWCpprCGZ1BTc3BbvpZvi6tkSteC8H3W/fMM6x+RoSunlzD3hEVV5bkbwdJIqNClLMchvyoPA==
+ dependencies:
+ "@firebase/component" "0.6.12"
+ "@firebase/storage" "0.13.5"
+ "@firebase/storage-types" "0.8.3"
+ "@firebase/util" "1.10.3"
+ tslib "^2.1.0"
+
+"@firebase/storage-types@0.8.3":
+ version "0.8.3"
+ resolved "https://registry.yarnpkg.com/@firebase/storage-types/-/storage-types-0.8.3.tgz#2531ef593a3452fc12c59117195d6485c6632d3d"
+ integrity sha512-+Muk7g9uwngTpd8xn9OdF/D48uiQ7I1Fae7ULsWPuKoCH3HU7bfFPhxtJYzyhjdniowhuDpQcfPmuNRAqZEfvg==
+
+"@firebase/storage@0.13.5":
+ version "0.13.5"
+ resolved "https://registry.yarnpkg.com/@firebase/storage/-/storage-0.13.5.tgz#108c86c9cd359aebd306882eb61ce6a8b1deb417"
+ integrity sha512-sB/7HNuW0N9tITyD0RxVLNCROuCXkml5i/iPqjwOGKC0xiUfpCOjBE+bb0ABMoN1qYZfqk0y9IuI2TdomjmkNw==
+ dependencies:
+ "@firebase/component" "0.6.12"
+ "@firebase/util" "1.10.3"
+ tslib "^2.1.0"
+
+"@firebase/util@1.10.3":
+ version "1.10.3"
+ resolved "https://registry.yarnpkg.com/@firebase/util/-/util-1.10.3.tgz#63fc5fea7b36236219c4875731597494416678d1"
+ integrity sha512-wfoF5LTy0m2ufUapV0ZnpcGQvuavTbJ5Qr1Ze9OJGL70cSMvhDyjS4w2121XdA3lGZSTOsDOyGhpoDtYwck85A==
+ dependencies:
+ tslib "^2.1.0"
+
+"@firebase/vertexai@1.0.3":
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/@firebase/vertexai/-/vertexai-1.0.3.tgz#815efed8c16105676ea090daed963be72a01fb68"
+ integrity sha512-SQHg/RPb3LwQs/xiLcvAZYz9NXyDSZUIIwvgsKh6e4wdULAfyPCZIu6Y2ZYIhZLfk9Q44cKZ+++7RPTaqQJdYA==
+ dependencies:
+ "@firebase/app-check-interop-types" "0.3.3"
+ "@firebase/component" "0.6.12"
+ "@firebase/logger" "0.4.4"
+ "@firebase/util" "1.10.3"
+ tslib "^2.1.0"
+
+"@firebase/webchannel-wrapper@1.0.3":
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/@firebase/webchannel-wrapper/-/webchannel-wrapper-1.0.3.tgz#a73bab8eb491d7b8b7be2f0e6c310647835afe83"
+ integrity sha512-2xCRM9q9FlzGZCdgDMJwc0gyUkWFtkosy7Xxr6sFgQwn+wMNIWd7xIvYNauU1r64B5L5rsGKy/n9TKJ0aAFeqQ==
+
"@floating-ui/core@^1.4.2":
version "1.5.2"
resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.5.2.tgz#53a0f7a98c550e63134d504f26804f6b83dbc071"
@@ -1518,6 +1908,24 @@
resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.1.6.tgz#22958c042e10b67463997bd6ea7115fe28cbcaf9"
integrity sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==
+"@grpc/grpc-js@~1.9.0":
+ version "1.9.15"
+ resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.9.15.tgz#433d7ac19b1754af690ea650ab72190bd700739b"
+ integrity sha512-nqE7Hc0AzI+euzUwDAy0aY5hCp10r734gMGRdU+qOPX0XSceI2ULrcXB5U2xSc5VkWwalCj4M7GzCAygZl2KoQ==
+ dependencies:
+ "@grpc/proto-loader" "^0.7.8"
+ "@types/node" ">=12.12.47"
+
+"@grpc/proto-loader@^0.7.8":
+ version "0.7.13"
+ resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.7.13.tgz#f6a44b2b7c9f7b609f5748c6eac2d420e37670cf"
+ integrity sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==
+ dependencies:
+ lodash.camelcase "^4.3.0"
+ long "^5.0.0"
+ protobufjs "^7.2.5"
+ yargs "^17.7.2"
+
"@jest/expect-utils@^29.7.0":
version "29.7.0"
resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.7.0.tgz#023efe5d26a8a70f21677d0a1afc0f0a44e3a1c6"
@@ -1628,6 +2036,59 @@
"@nodelib/fs.scandir" "2.1.5"
fastq "^1.6.0"
+"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2":
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf"
+ integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==
+
+"@protobufjs/base64@^1.1.2":
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735"
+ integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==
+
+"@protobufjs/codegen@^2.0.4":
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb"
+ integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==
+
+"@protobufjs/eventemitter@^1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70"
+ integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==
+
+"@protobufjs/fetch@^1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45"
+ integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==
+ dependencies:
+ "@protobufjs/aspromise" "^1.1.1"
+ "@protobufjs/inquire" "^1.1.0"
+
+"@protobufjs/float@^1.0.2":
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1"
+ integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==
+
+"@protobufjs/inquire@^1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089"
+ integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==
+
+"@protobufjs/path@^1.1.2":
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d"
+ integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==
+
+"@protobufjs/pool@^1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54"
+ integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==
+
+"@protobufjs/utf8@^1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570"
+ integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==
+
"@reach/portal@^0.13.0":
version "0.13.2"
resolved "https://registry.yarnpkg.com/@reach/portal/-/portal-0.13.2.tgz#6f2a6f4afc14894bde9c6435667bb9b660887ed9"
@@ -1995,6 +2456,13 @@
dependencies:
undici-types "~5.26.4"
+"@types/node@>=12.12.47", "@types/node@>=13.7.0":
+ version "22.10.10"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-22.10.10.tgz#85fe89f8bf459dc57dfef1689bd5b52ad1af07e6"
+ integrity sha512-X47y/mPNzxviAGY5TcYPtYL8JsY3kAq2n8fMmKoRCxq/c4v4pyGNCzM2R6+M5/umG4ZfHuT+sgqDYqWc9rJ6ww==
+ dependencies:
+ undici-types "~6.20.0"
+
"@types/parse-json@^4.0.0":
version "4.0.2"
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.2.tgz#5950e50960793055845e956c427fc2b0d70c5239"
@@ -2213,7 +2681,7 @@ acorn@^8.8.2:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.2.tgz#ca0d78b51895be5390a5903c5b3bdcdaf78ae40b"
integrity sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==
-ansi-regex@^5.0.0:
+ansi-regex@^5.0.0, ansi-regex@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
@@ -2606,6 +3074,15 @@ classnames@^2.2.5, classnames@^2.3.2:
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924"
integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==
+cliui@^8.0.1:
+ version "8.0.1"
+ resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa"
+ integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==
+ dependencies:
+ string-width "^4.2.0"
+ strip-ansi "^6.0.1"
+ wrap-ansi "^7.0.0"
+
clsx@^1.1.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12"
@@ -2887,6 +3364,11 @@ electron-to-chromium@^1.4.601:
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.614.tgz#2fe789d61fa09cb875569f37c309d0c2701f91c0"
integrity sha512-X4ze/9Sc3QWs6h92yerwqv7aB/uU8vCjZcrMjA8N9R1pjMFRe44dLsck5FzLilOYvcXuDn93B+bpGYyufc70gQ==
+emoji-regex@^8.0.0:
+ version "8.0.0"
+ resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
+ integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
+
emoji-regex@^9.2.2:
version "9.2.2"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72"
@@ -3338,6 +3820,13 @@ fastq@^1.6.0:
dependencies:
reusify "^1.0.4"
+faye-websocket@0.11.4:
+ version "0.11.4"
+ resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.4.tgz#7f0d9275cfdd86a1c963dc8b65fcc451edcbb1da"
+ integrity sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==
+ dependencies:
+ websocket-driver ">=0.5.1"
+
filelist@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5"
@@ -3357,6 +3846,40 @@ find-root@^1.1.0:
resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4"
integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==
+firebase@^11.2.0:
+ version "11.2.0"
+ resolved "https://registry.yarnpkg.com/firebase/-/firebase-11.2.0.tgz#843de48382fcaf8050a4a278f35d094fb9960fda"
+ integrity sha512-ztwPhBLAZMVNZjBeQzzTM4rk2rsRXmdFYcnvjAXh+StbiFVshHKaPO9VRGMUzF48du4Mkz6jN1wkmYCuUJPxLA==
+ dependencies:
+ "@firebase/analytics" "0.10.11"
+ "@firebase/analytics-compat" "0.2.17"
+ "@firebase/app" "0.10.18"
+ "@firebase/app-check" "0.8.11"
+ "@firebase/app-check-compat" "0.3.18"
+ "@firebase/app-compat" "0.2.48"
+ "@firebase/app-types" "0.9.3"
+ "@firebase/auth" "1.8.2"
+ "@firebase/auth-compat" "0.5.17"
+ "@firebase/data-connect" "0.2.0"
+ "@firebase/database" "1.0.11"
+ "@firebase/database-compat" "2.0.2"
+ "@firebase/firestore" "4.7.6"
+ "@firebase/firestore-compat" "0.3.41"
+ "@firebase/functions" "0.12.1"
+ "@firebase/functions-compat" "0.3.18"
+ "@firebase/installations" "0.6.12"
+ "@firebase/installations-compat" "0.2.12"
+ "@firebase/messaging" "0.12.16"
+ "@firebase/messaging-compat" "0.2.16"
+ "@firebase/performance" "0.6.12"
+ "@firebase/performance-compat" "0.2.12"
+ "@firebase/remote-config" "0.5.0"
+ "@firebase/remote-config-compat" "0.2.12"
+ "@firebase/storage" "0.13.5"
+ "@firebase/storage-compat" "0.3.15"
+ "@firebase/util" "1.10.3"
+ "@firebase/vertexai" "1.0.3"
+
focus-trap@^6.2.2:
version "6.9.4"
resolved "https://registry.yarnpkg.com/focus-trap/-/focus-trap-6.9.4.tgz#436da1a1d935c48b97da63cd8f361c6f3aa16444"
@@ -3406,6 +3929,11 @@ gensync@^1.0.0-beta.2:
resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==
+get-caller-file@^2.0.5:
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
+ integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
+
get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0, get-intrinsic@^1.2.1, get-intrinsic@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.2.tgz#281b7622971123e1ef4b3c90fd7539306da93f3b"
@@ -3545,6 +4073,16 @@ htmlparser2@^8.0.1:
domutils "^3.0.1"
entities "^4.4.0"
+http-parser-js@>=0.5.1:
+ version "0.5.9"
+ resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.9.tgz#b817b3ca0edea6236225000d795378707c169cec"
+ integrity sha512-n1XsPy3rXVxlqxVioEWdC+0+M+SQw0DpJynwtOPo1X+ZlvdzTLtDBIJJlDQTnwZIFJrZSzSGmIOUdP8tu+SgLw==
+
+idb@7.1.1:
+ version "7.1.1"
+ resolved "https://registry.yarnpkg.com/idb/-/idb-7.1.1.tgz#d910ded866d32c7ced9befc5bfdf36f572ced72b"
+ integrity sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==
+
ignore@^5.2.0:
version "5.3.0"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.0.tgz#67418ae40d34d6999c95ff56016759c718c82f78"
@@ -3654,6 +4192,11 @@ is-finalizationregistry@^1.0.2:
dependencies:
call-bind "^1.0.2"
+is-fullwidth-code-point@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
+ integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
+
is-generator-function@^1.0.10:
version "1.0.10"
resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72"
@@ -3929,6 +4472,11 @@ lines-and-columns@^1.1.6:
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632"
integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==
+lodash.camelcase@^4.3.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
+ integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==
+
lodash.debounce@^4.0.8:
version "4.0.8"
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
@@ -3939,6 +4487,11 @@ lodash@^4.17.15, lodash@^4.17.21:
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
+long@^5.0.0:
+ version "5.2.4"
+ resolved "https://registry.yarnpkg.com/long/-/long-5.2.4.tgz#ee651d5c7c25901cfca5e67220ae9911695e99b2"
+ integrity sha512-qtzLbJE8hq7VabR3mISmVGtoXP8KGc2Z/AT8OuqlYD7JTR3oqrgwdjnk07wpj1twXxYmgDXgoKVWUG/fReSzHg==
+
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
@@ -4497,6 +5050,24 @@ property-information@^6.0.0:
resolved "https://registry.yarnpkg.com/property-information/-/property-information-6.4.0.tgz#6bc4c618b0c2d68b3bb8b552cbb97f8e300a0f82"
integrity sha512-9t5qARVofg2xQqKtytzt+lZ4d1Qvj8t5B8fEwXK6qOfgRLgH/b13QlgEyDh033NOS31nXeFbYv7CLUDG1CeifQ==
+protobufjs@^7.2.5:
+ version "7.4.0"
+ resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.4.0.tgz#7efe324ce9b3b61c82aae5de810d287bc08a248a"
+ integrity sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==
+ dependencies:
+ "@protobufjs/aspromise" "^1.1.2"
+ "@protobufjs/base64" "^1.1.2"
+ "@protobufjs/codegen" "^2.0.4"
+ "@protobufjs/eventemitter" "^1.1.0"
+ "@protobufjs/fetch" "^1.1.0"
+ "@protobufjs/float" "^1.0.2"
+ "@protobufjs/inquire" "^1.1.0"
+ "@protobufjs/path" "^1.1.2"
+ "@protobufjs/pool" "^1.1.0"
+ "@protobufjs/utf8" "^1.1.0"
+ "@types/node" ">=13.7.0"
+ long "^5.0.0"
+
queue-microtask@^1.2.2:
version "1.2.3"
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
@@ -4806,6 +5377,11 @@ remark-rehype@^9.0.0:
mdast-util-to-hast "^11.0.0"
unified "^10.0.0"
+require-directory@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
+ integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==
+
resolve-from@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
@@ -4865,6 +5441,11 @@ safe-array-concat@^1.0.1:
has-symbols "^1.0.3"
isarray "^2.0.5"
+safe-buffer@>=5.1.0:
+ version "5.2.1"
+ resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
+ integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
+
safe-regex-test@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.0.tgz#793b874d524eb3640d1873aad03596db2d4f2295"
@@ -4982,6 +5563,15 @@ string-natural-compare@^3.0.1:
resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4"
integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==
+string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
+ version "4.2.3"
+ resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
+ integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
+ dependencies:
+ emoji-regex "^8.0.0"
+ is-fullwidth-code-point "^3.0.0"
+ strip-ansi "^6.0.1"
+
string.prototype.matchall@^4.0.8:
version "4.0.10"
resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.10.tgz#a1553eb532221d4180c51581d6072cd65d1ee100"
@@ -5024,6 +5614,13 @@ string.prototype.trimstart@^1.0.7:
define-properties "^1.2.0"
es-abstract "^1.22.1"
+strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
+ integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
+ dependencies:
+ ansi-regex "^5.0.1"
+
strip-bom@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
@@ -5227,6 +5824,11 @@ undici-types@~5.26.4:
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617"
integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==
+undici-types@~6.20.0:
+ version "6.20.0"
+ resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433"
+ integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==
+
unicode-canonical-property-names-ecmascript@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc"
@@ -5419,6 +6021,20 @@ web-vitals@^0.2.4:
resolved "https://registry.yarnpkg.com/web-vitals/-/web-vitals-0.2.4.tgz#ec3df43c834a207fd7cdefd732b2987896e08511"
integrity sha512-6BjspCO9VriYy12z356nL6JBS0GYeEcA457YyRzD+dD6XYCQ75NKhcOHUMHentOE7OcVCIXXDvOm0jKFfQG2Gg==
+websocket-driver@>=0.5.1:
+ version "0.7.4"
+ resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760"
+ integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==
+ dependencies:
+ http-parser-js ">=0.5.1"
+ safe-buffer ">=5.1.0"
+ websocket-extensions ">=0.1.1"
+
+websocket-extensions@>=0.1.1:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42"
+ integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==
+
which-boxed-primitive@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6"
@@ -5474,11 +6090,25 @@ word-wrap@~1.2.3:
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34"
integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==
+wrap-ansi@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
+ integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
+ dependencies:
+ ansi-styles "^4.0.0"
+ string-width "^4.1.0"
+ strip-ansi "^6.0.0"
+
xstate@^4.15.1:
version "4.38.3"
resolved "https://registry.yarnpkg.com/xstate/-/xstate-4.38.3.tgz#4e15e7ad3aa0ca1eea2010548a5379966d8f1075"
integrity sha512-SH7nAaaPQx57dx6qvfcIgqKRXIh4L0A1iYEqim4s1u7c9VoCgzZc+63FY90AKU4ZzOC2cfJzTnpO4zK7fCUzzw==
+y18n@^5.0.5:
+ version "5.0.8"
+ resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
+ integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
+
yallist@^3.0.2:
version "3.1.1"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
@@ -5494,6 +6124,24 @@ yaml@^1.10.0, yaml@^1.7.2:
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
+yargs-parser@^21.1.1:
+ version "21.1.1"
+ resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35"
+ integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==
+
+yargs@^17.7.2:
+ version "17.7.2"
+ resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269"
+ integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==
+ dependencies:
+ cliui "^8.0.1"
+ escalade "^3.1.1"
+ get-caller-file "^2.0.5"
+ require-directory "^2.1.1"
+ string-width "^4.2.3"
+ y18n "^5.0.5"
+ yargs-parser "^21.1.1"
+
zustand@^4.3.3:
version "4.4.7"
resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.4.7.tgz#355406be6b11ab335f59a66d2cf9815e8f24038c"