From c1c4ecd8ddd87b064ae50f7ae84293a748c13e48 Mon Sep 17 00:00:00 2001 From: mit-27 Date: Sat, 25 May 2024 02:06:53 -0400 Subject: [PATCH 1/9] =?UTF-8?q?=F0=9F=90=9B=20Fix=20auth=20forms=20UI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/client-ts/src/app/b2c/login/page.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/client-ts/src/app/b2c/login/page.tsx b/apps/client-ts/src/app/b2c/login/page.tsx index 5784cd977..bbc6c9848 100644 --- a/apps/client-ts/src/app/b2c/login/page.tsx +++ b/apps/client-ts/src/app/b2c/login/page.tsx @@ -54,9 +54,9 @@ export default function Page() { {!userInitialized ? (
-
+
- + Login Create Account From 1cfed03382a4f169cae808cf5047f50a4922643c Mon Sep 17 00:00:00 2001 From: mit-27 Date: Sun, 26 May 2024 02:40:18 -0400 Subject: [PATCH 2/9] =?UTF-8?q?=F0=9F=92=84=20Improve=20dashboard=20UI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/client-ts/src/app/b2c/login/page.tsx | 4 +- apps/client-ts/src/app/layout.tsx | 2 +- .../src/components/Nav/main-nav-sm.tsx | 41 +++++++++- .../client-ts/src/components/Nav/main-nav.tsx | 4 +- .../client-ts/src/components/Nav/user-nav.tsx | 75 +++++++++++++++--- .../src/components/RootLayout/index.tsx | 78 +++++++++++++++++-- .../src/components/shared/team-switcher.tsx | 2 +- 7 files changed, 182 insertions(+), 24 deletions(-) diff --git a/apps/client-ts/src/app/b2c/login/page.tsx b/apps/client-ts/src/app/b2c/login/page.tsx index bbc6c9848..aeb85e556 100644 --- a/apps/client-ts/src/app/b2c/login/page.tsx +++ b/apps/client-ts/src/app/b2c/login/page.tsx @@ -54,8 +54,10 @@ export default function Page() { {!userInitialized ? (
-
+
+
+
Login diff --git a/apps/client-ts/src/app/layout.tsx b/apps/client-ts/src/app/layout.tsx index f1ba7bb41..8eb767539 100644 --- a/apps/client-ts/src/app/layout.tsx +++ b/apps/client-ts/src/app/layout.tsx @@ -22,7 +22,7 @@ export default function RootLayout({ diff --git a/apps/client-ts/src/components/Nav/main-nav-sm.tsx b/apps/client-ts/src/components/Nav/main-nav-sm.tsx index 05e097ba6..9c643738a 100644 --- a/apps/client-ts/src/components/Nav/main-nav-sm.tsx +++ b/apps/client-ts/src/components/Nav/main-nav-sm.tsx @@ -11,6 +11,13 @@ import { import { MenuIcon } from "lucide-react" import { useState } from "react" import Link from "next/link" +import { useTheme } from 'next-themes'; +import {User,LogOut} from 'lucide-react'; +import { useRouter } from "next/navigation"; +import Cookies from 'js-cookie'; +import useProjectStore from "@/state/projectStore"; +import { useQueryClient } from '@tanstack/react-query'; +import useProfileStore from "@/state/profileStore"; export function SmallNav({ @@ -19,7 +26,12 @@ export function SmallNav({ onLinkClick: (name: string) => void }) { const [selectedItem, setSelectedItem] = useState("dashboard"); + const router = useRouter(); + const { profile, setProfile } = useProfileStore(); + const { setIdProject } = useProjectStore(); + const queryClient = useQueryClient(); const [open, setOpen] = useState(false); + const { theme } = useTheme() const navItemClassName = (itemName: string) => `text-sm border-b font-medium w-full text-left mx-0 py-2 dark:hover:bg-zinc-900 hover:bg-zinc-200 cursor-pointer ${ selectedItem === itemName ? "dark:bg-zinc-800 bg-zinc-200" : "text-muted-foreground" @@ -30,6 +42,15 @@ export function SmallNav({ onLinkClick(name); setOpen(false); } + + const onLogout = () => { + router.push('/b2c/login') + Cookies.remove("access_token") + setProfile(null) + setIdProject("") + queryClient.clear() + } + return (
@@ -39,8 +60,10 @@ export function SmallNav({ - setOpen(false)}> - Panora. + setOpen(false)}> + {/* Panora. */} + {theme == "light" ? : } + @@ -93,6 +116,20 @@ export function SmallNav({

Docs

+ click('b2c/profile')} + > + +

Profile

+
+ onLogout()} + > + +

Log Out

+
diff --git a/apps/client-ts/src/components/Nav/main-nav.tsx b/apps/client-ts/src/components/Nav/main-nav.tsx index 0c477c8c7..73bb49dff 100644 --- a/apps/client-ts/src/components/Nav/main-nav.tsx +++ b/apps/client-ts/src/components/Nav/main-nav.tsx @@ -19,7 +19,7 @@ export function MainNav({ }, [pathname]) const navItemClassName = (itemName: string) => - `group flex items-center rounded-md px-3 py-2 text-sm font-medium hover:bg-accent hover:text-accent-foreground cursor-pointer ${ + `group flex items-center rounded-md px-2 py-2 text-sm font-medium hover:bg-accent hover:text-accent-foreground cursor-pointer ${ selectedItem === itemName ? 'bg-accent' : 'transparent' } transition-colors`; @@ -63,7 +63,7 @@ export function MainNav({ target="_blank" rel="noopener noreferrer" > -

Documentation

+

Docs

diff --git a/apps/client-ts/src/components/Nav/user-nav.tsx b/apps/client-ts/src/components/Nav/user-nav.tsx index e42590d71..386520d9f 100644 --- a/apps/client-ts/src/components/Nav/user-nav.tsx +++ b/apps/client-ts/src/components/Nav/user-nav.tsx @@ -6,25 +6,36 @@ import { import { Button } from "@/components/ui/button" import { DropdownMenu, + DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, + DropdownMenuPortal, DropdownMenuSeparator, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu" import useProfileStore from "@/state/profileStore"; import { useRouter } from "next/navigation"; import Link from "next/link"; import Cookies from 'js-cookie'; -import useProjectStore from "@/state/projectStore" +import useProjectStore from "@/state/projectStore"; import { useQueryClient } from '@tanstack/react-query'; +import {User,LogOut,SunMoon,Sun,Moon,Monitor} from 'lucide-react'; +import {DotsHorizontalIcon} from '@radix-ui/react-icons'; +import { useTheme } from "next-themes"; + export function UserNav() { const router = useRouter(); const { profile, setProfile } = useProfileStore(); const { setIdProject } = useProjectStore(); const queryClient = useQueryClient(); + const { setTheme,theme } = useTheme(); + // const [currentTheme,SetCurrentTheme] = useState(theme) const onLogout = () => { router.push('/b2c/login') @@ -36,37 +47,79 @@ export function UserNav() { return ( - +

+ {profile ? `${profile.first_name} ${profile.last_name}` : ""} +

+ + +
+ {/* */} - - + + {/*

{profile ? profile.email || profile.first_name : "No profile found"}

-
- +
*/} + {/* */} - Profile + + Profile + + + + + Theme + + + + setTheme("light")} + > + + Light + + setTheme("dark")} + > + + Dark + + setTheme("system")} + > + + System + + + + + {/* Billing Settings */} - + {/* */} onLogout()} > - Log out + + Log Out
diff --git a/apps/client-ts/src/components/RootLayout/index.tsx b/apps/client-ts/src/components/RootLayout/index.tsx index a54968a2b..64f90ff34 100644 --- a/apps/client-ts/src/components/RootLayout/index.tsx +++ b/apps/client-ts/src/components/RootLayout/index.tsx @@ -13,10 +13,15 @@ import { ThemeToggle } from '@/components/Nav/theme-toggle'; import useProjects from '@/hooks/get/useProjects'; import useRefreshAccessTokenMutation from '@/hooks/create/useRefreshAccessToken'; import { useTheme } from 'next-themes'; +import { useState } from "react"; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; +import { Button } from "@/components/ui/button"; +import { toast } from "sonner"; export const RootLayout = ({children}:{children:React.ReactNode}) => { const router = useRouter() const base = process.env.NEXT_PUBLIC_WEBAPP_DOMAIN; + const [copiesProjectID, SetCopiesProjectID] = useState(false); const {data : projectsData} = useProjects(); const { idProject, setIdProject } = useProjectStore(); const {mutate : refreshAccessToken} = useRefreshAccessTokenMutation() @@ -42,9 +47,23 @@ export const RootLayout = ({children}:{children:React.ReactNode}) => { } }; + const handleCopyRight = () => { + navigator.clipboard.writeText(idProject); + toast.success("Project ID copied!", { + action: { + label: "Close", + onClick: () => console.log("Close"), + }, + }) + SetCopiesProjectID(true); + setTimeout(() => { + SetCopiesProjectID(false); + }, 2000); + }; + return ( <> -
+ {/*
-
+
*/}
+ -
{children}
+
+ +
+
{children}
diff --git a/apps/client-ts/src/components/shared/team-switcher.tsx b/apps/client-ts/src/components/shared/team-switcher.tsx index e1b3826e1..2ec1b460e 100644 --- a/apps/client-ts/src/components/shared/team-switcher.tsx +++ b/apps/client-ts/src/components/shared/team-switcher.tsx @@ -170,7 +170,7 @@ export default function TeamSwitcher({ className ,projects}: TeamSwitcherProps) - + From 70309cd94f0f824a9d80e7dfabce87e15006fa15 Mon Sep 17 00:00:00 2001 From: mit-27 Date: Sun, 26 May 2024 17:10:53 -0400 Subject: [PATCH 3/9] =?UTF-8?q?=F0=9F=92=84=20Added=20success=20connection?= =?UTF-8?q?=20dialog=20for=20magic=20link?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/magic-link/src/App.tsx | 2 +- apps/magic-link/src/components/Modal.tsx | 34 ++++++++++++++++++++ apps/magic-link/src/hooks/useOAuth.ts | 16 ++++++++-- apps/magic-link/src/lib/ProviderModal.tsx | 39 +++++++++++++++++++++-- 4 files changed, 85 insertions(+), 6 deletions(-) create mode 100644 apps/magic-link/src/components/Modal.tsx diff --git a/apps/magic-link/src/App.tsx b/apps/magic-link/src/App.tsx index e815e5f7a..a8b5b1a77 100644 --- a/apps/magic-link/src/App.tsx +++ b/apps/magic-link/src/App.tsx @@ -5,7 +5,7 @@ import { ThemeProvider } from "@/components/theme-provider" function App() { return ( -
+
diff --git a/apps/magic-link/src/components/Modal.tsx b/apps/magic-link/src/components/Modal.tsx new file mode 100644 index 000000000..348b83da1 --- /dev/null +++ b/apps/magic-link/src/components/Modal.tsx @@ -0,0 +1,34 @@ +"use client" +import React, { useState } from 'react' +import {PartyPopper, Unplug,X} from 'lucide-react' + +const Modal = ({open,setOpen,children} : {open:boolean,setOpen: (op : boolean) => void,children: React.ReactNode}) => { + return ( +
setOpen(false)} + className={` + fixed inset-0 flex justify-center items-center transition-colors + ${open ? "visible bg-black/20 backdrop-blur" : "invisible"} + `} + > + {/* modal */} +
e.stopPropagation()} + className={` + bg-[#1d1d1d] border-green-900 rounded-xl shadow p-6 transition-all + ${open ? "scale-100 opacity-100" : "scale-125 opacity-0"} + `} + > + {/* */} + {children} +
+
+ ) +} + +export default Modal \ No newline at end of file diff --git a/apps/magic-link/src/hooks/useOAuth.ts b/apps/magic-link/src/hooks/useOAuth.ts index ef590fc9c..d38272cbd 100644 --- a/apps/magic-link/src/hooks/useOAuth.ts +++ b/apps/magic-link/src/hooks/useOAuth.ts @@ -34,14 +34,26 @@ const useOAuth = ({ providerName, vertical, returnUrl, projectId, linkedUserId, const left = (window.innerWidth - width) / 2; const top = (window.innerHeight - height) / 2; const authWindow = window.open(authUrl as string, 'OAuth', `width=${width},height=${height},top=${top},left=${left}`); - + const interval = setInterval(() => { + try { + const redirectedURL = authWindow!.location.protocol + '//' + authWindow!.location.hostname + (authWindow!.location.port ? ':' + authWindow!.location.port : ''); + console.log("Redirected URL auth 0Auth : ", redirectedURL, " Actual returnURL : ", returnUrl); + if (redirectedURL === returnUrl) { + clearInterval(interval); + authWindow!.close(); + onSuccess(); + } + } catch (e) { + // To handle cross-origin error + console.log(e) + } + if (authWindow!.closed) { clearInterval(interval); if (onWindowClose) { onWindowClose(); } - onSuccess(); } }, 500); diff --git a/apps/magic-link/src/lib/ProviderModal.tsx b/apps/magic-link/src/lib/ProviderModal.tsx index 98befc4e6..ca8c6a61c 100644 --- a/apps/magic-link/src/lib/ProviderModal.tsx +++ b/apps/magic-link/src/lib/ProviderModal.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from 'react'; import useOAuth from '@/hooks/useOAuth'; -import { findProviderByName, providersArray, categoryFromSlug, Provider } from '@panora/shared/src'; +import { findProviderByName, providersArray, categoryFromSlug, Provider,CONNECTORS_METADATA } from '@panora/shared/src'; import { categoriesVerticals } from '@panora/shared/src/categories'; import useLinkedUser from '@/hooks/queries/useLinkedUser'; import useUniqueMagicLink from '@/hooks/queries/useUniqueMagicLink'; @@ -11,6 +11,8 @@ import { Button } from '@/components/ui/button'; import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectSeparator, SelectTrigger, SelectValue } from '@/components/ui/select'; import { LoadingSpinner } from '@/components/ui/loading-spinner'; import useProjectConnectors from '@/hooks/queries/useProjectConnectors'; +import {Unplug,X} from 'lucide-react' +import Modal from '@/components/Modal'; const LoadingOverlay = ({ providerName }: { providerName: string }) => { @@ -48,6 +50,8 @@ const ProviderModal = () => { }>({status: false, provider: ''}); const [uniqueMagicLinkId, setUniqueMagicLinkId] = useState(''); + const [openSuccessDialog,setOpenSuccessDialog] = useState(false); + const [currentProviderLogoURL,setCurrentProviderLogoURL] = useState('') const {data: magicLink} = useUniqueMagicLink(uniqueMagicLinkId); const {data: connectorsForProject} = useProjectConnectors(projectId); @@ -90,10 +94,15 @@ const ProviderModal = () => { const { open, isReady } = useOAuth({ providerName: selectedProvider?.provider!, vertical: selectedProvider?.category!, - returnUrl: "https://google.com", + returnUrl: window.location.protocol + '//' + window.location.hostname + (window.location.port ? ':' + window.location.port : ''), + // returnUrl: "https://google.com", + // returnUrl: window.location.hostname + (window.location.port ? ':' + window.location.port : ''), projectId: projectId, linkedUserId: magicLink?.id_linked_user as string, - onSuccess: () => console.log('OAuth successful'), + onSuccess: () => { + console.log('OAuth successful'); + setOpenSuccessDialog(true); + }, }); const onWindowClose = () => { @@ -124,6 +133,8 @@ const ProviderModal = () => { const handleWalletClick = (walletName: string, category: string) => { setSelectedProvider({provider: walletName.toLowerCase(), category: category.toLowerCase()}); + const logoPath = CONNECTORS_METADATA[category.toLowerCase()][walletName.toLowerCase()].logoPath; + setCurrentProviderLogoURL(logoPath) setPreStartFlow(true); }; @@ -158,6 +169,7 @@ const ProviderModal = () => { return ( + <> Connect to your software @@ -209,6 +221,27 @@ const ProviderModal = () => { {loading.status ? : } + + {/* OAuth Successful Modal */} + +
+
+
+ + + + {selectedProvider?.provider} + +
+ +
Connected !
+ +
The connection was successfully established. You can visit the Dashboard and verify the status.
+ +
+
+
+ ); }; From 7cca963c81ce419b7344720ec70db41a51a8c28b Mon Sep 17 00:00:00 2001 From: mit-27 Date: Mon, 27 May 2024 12:04:16 -0400 Subject: [PATCH 4/9] =?UTF-8?q?=F0=9F=8E=A8=20Added=20success=20dialog=20a?= =?UTF-8?q?nd=20automatic=20window=20close=20on=20succeessful=20auth?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../react/src/components/Modal.tsx | 33 ++++++ .../src/components/PanoraDynamicCatalog.tsx | 44 +++++++- .../src/components/PanoraIntegrationCard.tsx | 41 ++++++- .../hooks/queries/useProjectConnectors.tsx | 4 +- .../react/src/hooks/useOAuth.tsx | 60 +++++++++-- apps/magic-link/src/hooks/useOAuth.ts | 102 +++++++++++------- apps/magic-link/src/lib/ProviderModal.tsx | 5 +- 7 files changed, 226 insertions(+), 63 deletions(-) create mode 100644 apps/embedded-catalog/react/src/components/Modal.tsx diff --git a/apps/embedded-catalog/react/src/components/Modal.tsx b/apps/embedded-catalog/react/src/components/Modal.tsx new file mode 100644 index 000000000..b27cd538d --- /dev/null +++ b/apps/embedded-catalog/react/src/components/Modal.tsx @@ -0,0 +1,33 @@ +import React, { useState } from 'react' +import {PartyPopper, Unplug,X} from 'lucide-react' + +const Modal = ({open,setOpen,children} : {open:boolean,setOpen: (op : boolean) => void,children: React.ReactNode}) => { + return ( +
setOpen(false)} + className={` + fixed inset-0 flex justify-center items-center transition-colors + ${open ? "visible bg-black/20 backdrop-blur" : "invisible"} + `} + > + {/* modal */} +
e.stopPropagation()} + className={` + bg-[#1d1d1d] border-green-900 rounded-xl shadow p-6 transition-all + ${open ? "scale-100 opacity-100" : "scale-125 opacity-0"} + `} + > + {/* */} + {children} +
+
+ ) +} + +export default Modal \ No newline at end of file diff --git a/apps/embedded-catalog/react/src/components/PanoraDynamicCatalog.tsx b/apps/embedded-catalog/react/src/components/PanoraDynamicCatalog.tsx index 16b09ffca..2e5807864 100644 --- a/apps/embedded-catalog/react/src/components/PanoraDynamicCatalog.tsx +++ b/apps/embedded-catalog/react/src/components/PanoraDynamicCatalog.tsx @@ -1,10 +1,13 @@ import {useState,useEffect} from 'react' -import {providersArray, ConnectorCategory, categoryFromSlug, Provider} from '@panora/shared'; +import {providersArray, ConnectorCategory, categoryFromSlug, Provider,CONNECTORS_METADATA} from '@panora/shared'; import useOAuth from '@/hooks/useOAuth'; import useProjectConnectors from '@/hooks/queries/useProjectConnectors'; import { Card } from './ui/card'; import { Button } from './ui/button2' import { ArrowRightIcon } from '@radix-ui/react-icons'; +import {Unplug,X} from 'lucide-react' +import Modal from './Modal'; +import config from '@/helpers/config'; export interface DynamicCardProp { projectId: string; @@ -29,20 +32,30 @@ const DynamicCatalog = ({projectId,returnUrl,linkedUserId, category, optionalApi const [error,setError] = useState(false); const [startFlow, setStartFlow] = useState(false); + const [openSuccessDialog,setOpenSuccessDialog] = useState(false); + const [currentProviderLogoURL,setCurrentProviderLogoURL] = useState('') + const returnUrlWithWindow = (typeof window !== 'undefined') + ? window.location.protocol + '//' + window.location.hostname + (window.location.port ? ':' + window.location.port : '') + : ''; + const [data, setData] = useState([]); const { open, isReady } = useOAuth({ providerName: selectedProvider?.provider!, vertical: selectedProvider?.category! as ConnectorCategory, - returnUrl: returnUrl, + returnUrl: returnUrlWithWindow, + // returnUrl: returnUrl, projectId: projectId, linkedUserId: linkedUserId, optionalApiUrl: optionalApiUrl, - onSuccess: () => console.log('OAuth successful'), + onSuccess: () => { + console.log('OAuth successful'); + setOpenSuccessDialog(true); + }, }); - const {data: connectorsForProject} = useProjectConnectors(projectId); + const {data: connectorsForProject} = useProjectConnectors(projectId,optionalApiUrl ? optionalApiUrl : config.API_URL!); const onWindowClose = () => { setSelectedProvider({ @@ -88,6 +101,8 @@ const DynamicCatalog = ({projectId,returnUrl,linkedUserId, category, optionalApi const handleStartFlow = (walletName: string, category: string) => { setSelectedProvider({provider: walletName.toLowerCase(), category: category.toLowerCase()}); + const logoPath = CONNECTORS_METADATA[category.toLowerCase()][walletName.toLowerCase()].logoPath; + setCurrentProviderLogoURL(logoPath) setLoading({status: true, provider: selectedProvider?.provider!}); setStartFlow(true); } @@ -138,6 +153,27 @@ const DynamicCatalog = ({projectId,returnUrl,linkedUserId, category, optionalApi )}) } + + + {/* OAuth Successful Modal */} + +
+
+
+ + + + {selectedProvider?.provider} + +
+ +
Connection Successful!
+ +
The connection was successfully established. You can visit the Dashboard and verify the status.
+ +
+
+
) } diff --git a/apps/embedded-catalog/react/src/components/PanoraIntegrationCard.tsx b/apps/embedded-catalog/react/src/components/PanoraIntegrationCard.tsx index 5b385f52f..8762e272e 100644 --- a/apps/embedded-catalog/react/src/components/PanoraIntegrationCard.tsx +++ b/apps/embedded-catalog/react/src/components/PanoraIntegrationCard.tsx @@ -4,6 +4,8 @@ import { CONNECTORS_METADATA, ConnectorCategory } from '@panora/shared'; import { Button } from './ui/button2'; import { Card } from './ui/card'; import { ArrowRightIcon } from '@radix-ui/react-icons'; +import {Unplug,X} from 'lucide-react' +import Modal from './Modal'; export interface ProviderCardProp { name: string; @@ -15,22 +17,33 @@ export interface ProviderCardProp { } const PanoraIntegrationCard = ({name, category, projectId, returnUrl, linkedUserId, optionalApiUrl}: ProviderCardProp) => { - const [loading, setLoading] = useState(false) + const [loading, setLoading] = useState(false); + const [openSuccessDialog,setOpenSuccessDialog] = useState(false); const [startFlow, setStartFlow] = useState(false); + const returnUrlWithWindow = (typeof window !== 'undefined') + ? window.location.protocol + '//' + window.location.hostname + (window.location.port ? ':' + window.location.port : '') + : ''; + const { open, isReady } = useOAuth({ providerName: name.toLowerCase(), vertical: category.toLowerCase() as ConnectorCategory, - returnUrl: returnUrl, + returnUrl: returnUrlWithWindow, + // returnUrl: window.location.protocol + '//' + window.location.hostname + (window.location.port ? ':' + window.location.port : ''), + // returnUrl: returnUrl, projectId: projectId, linkedUserId: linkedUserId, optionalApiUrl: optionalApiUrl, - onSuccess: () => console.log('OAuth successful'), + onSuccess: () => { + console.log('OAuth successful'); + setOpenSuccessDialog(true); + }, }); const onWindowClose = () => { setLoading(false); - return; + setStartFlow(false); + // return; } useEffect(() => { @@ -78,6 +91,26 @@ const PanoraIntegrationCard = ({name, category, projectId, returnUrl, linkedUser
+ + {/* OAuth Successful Modal */} + +
+
+
+ + + + {name} + +
+ +
Connection Successful!
+ +
The connection was successfully established. You can visit the Dashboard and verify the status.
+ +
+
+
) }; diff --git a/apps/embedded-catalog/react/src/hooks/queries/useProjectConnectors.tsx b/apps/embedded-catalog/react/src/hooks/queries/useProjectConnectors.tsx index 1949f8162..34de913ae 100644 --- a/apps/embedded-catalog/react/src/hooks/queries/useProjectConnectors.tsx +++ b/apps/embedded-catalog/react/src/hooks/queries/useProjectConnectors.tsx @@ -1,11 +1,11 @@ import { useQuery } from '@tanstack/react-query'; import config from '@/helpers/config'; -const useProjectConnectors = (id: string) => { +const useProjectConnectors = (id: string,API_URL : string) => { return useQuery({ queryKey: ['project-connectors', id], queryFn: async (): Promise => { - const response = await fetch(`${config.API_URL}/project-connectors?projectId=${id}`); + const response = await fetch(`${API_URL}/project-connectors?projectId=${id}`); if (!response.ok) { throw new Error('Network response was not ok'); } diff --git a/apps/embedded-catalog/react/src/hooks/useOAuth.tsx b/apps/embedded-catalog/react/src/hooks/useOAuth.tsx index 6209b069b..e94bb754e 100644 --- a/apps/embedded-catalog/react/src/hooks/useOAuth.tsx +++ b/apps/embedded-catalog/react/src/hooks/useOAuth.tsx @@ -1,11 +1,11 @@ import config from '@/helpers/config'; -import { useState, useEffect } from 'react'; +import { useState, useEffect, useRef } from 'react'; import { ConnectorCategory, constructAuthUrl } from '@panora/shared'; type UseOAuthProps = { clientId?: string; providerName: string; // Name of the OAuth provider - vertical: ConnectorCategory; // Vertical (Crm, Ticketing, etc) + vertical: ConnectorCategory; // Vertical (Crm, Ticketing, etc) returnUrl: string; // Return URL after OAuth flow projectId: string; // Project ID linkedUserId: string; // Linked User ID @@ -15,36 +15,76 @@ type UseOAuthProps = { const useOAuth = ({ providerName, vertical, returnUrl, projectId, linkedUserId, optionalApiUrl, onSuccess }: UseOAuthProps) => { const [isReady, setIsReady] = useState(false); - + const intervalRef = useRef(null); + const authWindowRef = useRef(null); useEffect(() => { // Perform any setup logic here setTimeout(() => setIsReady(true), 1000); // Simulating async operation + + return () => { + // Cleanup on unmount + clearExistingInterval(false); + if (authWindowRef.current && !authWindowRef.current.closed) { + authWindowRef.current.close(); + } + }; }, []); + const clearExistingInterval = (clearAuthWindow : boolean) => { + if (clearAuthWindow && authWindowRef.current && !authWindowRef.current.closed) { + authWindowRef.current.close(); + } + if (intervalRef.current !== null) { + clearInterval(intervalRef.current); + intervalRef.current = null; + } + }; + const openModal = async (onWindowClose: () => void) => { - const apiUrl = optionalApiUrl? optionalApiUrl : config.API_URL!; + const apiUrl = optionalApiUrl ? optionalApiUrl : config.API_URL!; const authUrl = await constructAuthUrl({ projectId, linkedUserId, providerName, returnUrl, apiUrl, vertical }); - if(!authUrl) { - throw new Error("Auth Url is Invalid "+ authUrl) + + if (!authUrl) { + throw new Error("Auth Url is Invalid " + authUrl); } + const width = 600, height = 600; const left = (window.innerWidth - width) / 2; const top = (window.innerHeight - height) / 2; const authWindow = window.open(authUrl as string, 'OAuth', `width=${width},height=${height},top=${top},left=${left}`); - + authWindowRef.current = authWindow; + + clearExistingInterval(false); + const interval = setInterval(() => { - if (authWindow!.closed) { - clearInterval(interval); + try{ + const redirectedURL = authWindow!.location.protocol + '//' + authWindow!.location.hostname + (authWindow!.location.port ? ':' + authWindow!.location.port : ''); + if(redirectedURL===returnUrl) + { + onSuccess(); + clearExistingInterval(true); + } + + } catch(e) + { + console.log(e) + } + if (!authWindow || authWindow.closed) { if (onWindowClose) { onWindowClose(); } - onSuccess(); + authWindowRef.current = null; + console.log("Clearing direct close interval") + clearExistingInterval(false); } + }, 500); + intervalRef.current = interval; + return authWindow; }; diff --git a/apps/magic-link/src/hooks/useOAuth.ts b/apps/magic-link/src/hooks/useOAuth.ts index d38272cbd..87a900d40 100644 --- a/apps/magic-link/src/hooks/useOAuth.ts +++ b/apps/magic-link/src/hooks/useOAuth.ts @@ -1,67 +1,89 @@ import config from '@/helpers/config'; -import { useState, useEffect } from 'react'; -import { constructAuthUrl } from '@panora/shared/src/test'; +import { useState, useEffect, useRef } from 'react'; +import { ConnectorCategory, constructAuthUrl } from '@panora/shared/src/test'; type UseOAuthProps = { clientId?: string; providerName: string; // Name of the OAuth provider - vertical: string; + vertical: ConnectorCategory; // Vertical (Crm, Ticketing, etc) returnUrl: string; // Return URL after OAuth flow projectId: string; // Project ID linkedUserId: string; // Linked User ID + optionalApiUrl?: string; // URL of the User's Server onSuccess: () => void; }; -const useOAuth = ({ providerName, vertical, returnUrl, projectId, linkedUserId, onSuccess }: UseOAuthProps) => { +const useOAuth = ({ providerName, vertical, returnUrl, projectId, linkedUserId, optionalApiUrl, onSuccess }: UseOAuthProps) => { const [isReady, setIsReady] = useState(false); + const intervalRef = useRef(null); + const authWindowRef = useRef(null); useEffect(() => { // Perform any setup logic here setTimeout(() => setIsReady(true), 1000); // Simulating async operation + + return () => { + // Cleanup on unmount + clearExistingInterval(false); + if (authWindowRef.current && !authWindowRef.current.closed) { + authWindowRef.current.close(); + } + }; }, []); + const clearExistingInterval = (clearAuthWindow: boolean) => { + if (clearAuthWindow && authWindowRef.current && !authWindowRef.current.closed) { + authWindowRef.current.close(); + } + if (intervalRef.current !== null) { + clearInterval(intervalRef.current); + intervalRef.current = null; + } + }; const openModal = async (onWindowClose: () => void) => { - try { - const apiUrl = config.API_URL; - const authUrl = await constructAuthUrl({ - projectId, linkedUserId, providerName, returnUrl, apiUrl, vertical - }); - if (!authUrl) { - throw new Error("Auth Url is Invalid " + authUrl); - } - const width = 600, height = 600; - const left = (window.innerWidth - width) / 2; - const top = (window.innerHeight - height) / 2; - const authWindow = window.open(authUrl as string, 'OAuth', `width=${width},height=${height},top=${top},left=${left}`); - - const interval = setInterval(() => { - try { - const redirectedURL = authWindow!.location.protocol + '//' + authWindow!.location.hostname + (authWindow!.location.port ? ':' + authWindow!.location.port : ''); - console.log("Redirected URL auth 0Auth : ", redirectedURL, " Actual returnURL : ", returnUrl); - if (redirectedURL === returnUrl) { - clearInterval(interval); - authWindow!.close(); - onSuccess(); - } - } catch (e) { - // To handle cross-origin error - console.log(e) + const apiUrl = optionalApiUrl ? optionalApiUrl : config.API_URL!; + const authUrl = await constructAuthUrl({ + projectId, linkedUserId, providerName, returnUrl, apiUrl, vertical + }); + + if (!authUrl) { + throw new Error("Auth Url is Invalid " + authUrl); + } + + const width = 600, height = 600; + const left = (window.innerWidth - width) / 2; + const top = (window.innerHeight - height) / 2; + const authWindow = window.open(authUrl as string, 'OAuth', `width=${width},height=${height},top=${top},left=${left}`); + authWindowRef.current = authWindow; + + clearExistingInterval(false); + + const interval = setInterval(() => { + try { + const redirectedURL = authWindow!.location.protocol + '//' + authWindow!.location.hostname + (authWindow!.location.port ? ':' + authWindow!.location.port : ''); + if (redirectedURL === returnUrl) { + onSuccess(); + clearExistingInterval(true); } - if (authWindow!.closed) { - clearInterval(interval); - if (onWindowClose) { - onWindowClose(); - } + } catch (e) { + console.log(e) + } + if (!authWindow || authWindow.closed) { + if (onWindowClose) { + onWindowClose(); } - }, 500); + authWindowRef.current = null; + console.log("Clearing direct close interval") + clearExistingInterval(false); + } - return authWindow; - } catch (error) { - console.error('Failed to open OAuth window', error); - onWindowClose(); // Reset the loading state - } + }, 500); + + intervalRef.current = interval; + + return authWindow; }; return { open: openModal, isReady }; diff --git a/apps/magic-link/src/lib/ProviderModal.tsx b/apps/magic-link/src/lib/ProviderModal.tsx index ca8c6a61c..90505c41f 100644 --- a/apps/magic-link/src/lib/ProviderModal.tsx +++ b/apps/magic-link/src/lib/ProviderModal.tsx @@ -94,9 +94,8 @@ const ProviderModal = () => { const { open, isReady } = useOAuth({ providerName: selectedProvider?.provider!, vertical: selectedProvider?.category!, + // Providing current URL to avoid cross origin error returnUrl: window.location.protocol + '//' + window.location.hostname + (window.location.port ? ':' + window.location.port : ''), - // returnUrl: "https://google.com", - // returnUrl: window.location.hostname + (window.location.port ? ':' + window.location.port : ''), projectId: projectId, linkedUserId: magicLink?.id_linked_user as string, onSuccess: () => { @@ -234,7 +233,7 @@ const ProviderModal = () => {
-
Connected !
+
Connection Successful!
The connection was successfully established. You can visit the Dashboard and verify the status.
From 03e5114881e72f889b70494138d16b827d144652 Mon Sep 17 00:00:00 2001 From: mit-27 Date: Mon, 27 May 2024 12:59:23 -0400 Subject: [PATCH 5/9] =?UTF-8?q?=F0=9F=92=9A=20Fix=20build?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/embedded-catalog/react/package.json | 1 + apps/magic-link/src/hooks/useOAuth.ts | 6 +++--- pnpm-lock.yaml | 3 +++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/embedded-catalog/react/package.json b/apps/embedded-catalog/react/package.json index 93b967b44..efd8bdcdf 100644 --- a/apps/embedded-catalog/react/package.json +++ b/apps/embedded-catalog/react/package.json @@ -22,6 +22,7 @@ }, "dependencies": { "@panora/shared": "workspace:^", + "lucide-react": "^0.344.0", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-slot": "^1.0.2", "@tanstack/react-query": "^5.12.2", diff --git a/apps/magic-link/src/hooks/useOAuth.ts b/apps/magic-link/src/hooks/useOAuth.ts index 87a900d40..5f39fe7d8 100644 --- a/apps/magic-link/src/hooks/useOAuth.ts +++ b/apps/magic-link/src/hooks/useOAuth.ts @@ -1,11 +1,11 @@ import config from '@/helpers/config'; import { useState, useEffect, useRef } from 'react'; -import { ConnectorCategory, constructAuthUrl } from '@panora/shared/src/test'; +import { constructAuthUrl } from '@panora/shared/src/test'; type UseOAuthProps = { clientId?: string; providerName: string; // Name of the OAuth provider - vertical: ConnectorCategory; // Vertical (Crm, Ticketing, etc) + vertical: string; // Vertical (Crm, Ticketing, etc) returnUrl: string; // Return URL after OAuth flow projectId: string; // Project ID linkedUserId: string; // Linked User ID @@ -15,7 +15,7 @@ type UseOAuthProps = { const useOAuth = ({ providerName, vertical, returnUrl, projectId, linkedUserId, optionalApiUrl, onSuccess }: UseOAuthProps) => { const [isReady, setIsReady] = useState(false); - const intervalRef = useRef(null); + const intervalRef = useRef | null>(null); const authWindowRef = useRef(null); useEffect(() => { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9515fb60a..cf5aa1096 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -244,6 +244,9 @@ importers: clsx: specifier: ^2.1.0 version: 2.1.0 + lucide-react: + specifier: ^0.344.0 + version: 0.344.0(react@18.2.0) react-loader-spinner: specifier: ^5.4.5 version: 5.5.0(@babel/core@7.24.4)(react-dom@18.2.0)(react@18.2.0) From f95dca810765d1c1203c01a1049af94f74eb4430 Mon Sep 17 00:00:00 2001 From: mit-27 Date: Mon, 27 May 2024 17:49:27 -0400 Subject: [PATCH 6/9] =?UTF-8?q?=F0=9F=94=A5=20Removed=20returnUrl=20prop?= =?UTF-8?q?=20from=20embedded-catalog?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../react/src/components/PanoraDynamicCatalog.tsx | 3 +-- .../react/src/components/PanoraIntegrationCard.tsx | 3 +-- apps/embedded-catalog/react/src/index.tsx | 8 ++++---- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/apps/embedded-catalog/react/src/components/PanoraDynamicCatalog.tsx b/apps/embedded-catalog/react/src/components/PanoraDynamicCatalog.tsx index 2e5807864..503d8b319 100644 --- a/apps/embedded-catalog/react/src/components/PanoraDynamicCatalog.tsx +++ b/apps/embedded-catalog/react/src/components/PanoraDynamicCatalog.tsx @@ -11,13 +11,12 @@ import config from '@/helpers/config'; export interface DynamicCardProp { projectId: string; - returnUrl: string; linkedUserId: string; category?: ConnectorCategory; optionalApiUrl?: string, } -const DynamicCatalog = ({projectId,returnUrl,linkedUserId, category, optionalApiUrl} : DynamicCardProp) => { +const DynamicCatalog = ({projectId,linkedUserId, category, optionalApiUrl} : DynamicCardProp) => { // by default we render all integrations but if category is provided we filter by category diff --git a/apps/embedded-catalog/react/src/components/PanoraIntegrationCard.tsx b/apps/embedded-catalog/react/src/components/PanoraIntegrationCard.tsx index 8762e272e..b939a6437 100644 --- a/apps/embedded-catalog/react/src/components/PanoraIntegrationCard.tsx +++ b/apps/embedded-catalog/react/src/components/PanoraIntegrationCard.tsx @@ -11,12 +11,11 @@ export interface ProviderCardProp { name: string; category: ConnectorCategory; projectId: string; - returnUrl: string; linkedUserId: string; optionalApiUrl?: string, } -const PanoraIntegrationCard = ({name, category, projectId, returnUrl, linkedUserId, optionalApiUrl}: ProviderCardProp) => { +const PanoraIntegrationCard = ({name, category, projectId, linkedUserId, optionalApiUrl}: ProviderCardProp) => { const [loading, setLoading] = useState(false); const [openSuccessDialog,setOpenSuccessDialog] = useState(false); const [startFlow, setStartFlow] = useState(false); diff --git a/apps/embedded-catalog/react/src/index.tsx b/apps/embedded-catalog/react/src/index.tsx index 225dfd62c..74f1fb936 100644 --- a/apps/embedded-catalog/react/src/index.tsx +++ b/apps/embedded-catalog/react/src/index.tsx @@ -4,20 +4,20 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import PanoraDynamicCatalog, { DynamicCardProp } from "./components/PanoraDynamicCatalog"; -const PanoraProviderCard = ({name, category, projectId, returnUrl, linkedUserId, optionalApiUrl}: ProviderCardProp) => { +const PanoraProviderCard = ({name, category, projectId, linkedUserId, optionalApiUrl}: ProviderCardProp) => { const queryClient = new QueryClient(); return ( - + ) } -const PanoraDynamicCatalogCard = ({projectId, returnUrl, linkedUserId, category, optionalApiUrl} : DynamicCardProp) => { +const PanoraDynamicCatalogCard = ({projectId, linkedUserId, category, optionalApiUrl} : DynamicCardProp) => { const queryClient = new QueryClient(); return ( - + ) From b50bde615865b4de30a28a2c98c9a8a07dd71e93 Mon Sep 17 00:00:00 2001 From: mit-27 Date: Mon, 27 May 2024 21:57:50 -0400 Subject: [PATCH 7/9] =?UTF-8?q?=F0=9F=90=9B=20Fix=20bugs=20and=20added=20c?= =?UTF-8?q?opy=20feat=20for=20linkedUserId?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Configuration/LinkedUsers/columns.tsx | 42 +++++++++++++++---- .../src/components/Nav/main-nav-sm.tsx | 13 ------ .../react/src/components/Modal.tsx | 5 +-- .../src/components/PanoraDynamicCatalog.tsx | 1 - .../react/src/hooks/useOAuth.tsx | 24 ++++++----- 5 files changed, 50 insertions(+), 35 deletions(-) diff --git a/apps/client-ts/src/components/Configuration/LinkedUsers/columns.tsx b/apps/client-ts/src/components/Configuration/LinkedUsers/columns.tsx index 36528c49d..0cf0c83e7 100644 --- a/apps/client-ts/src/components/Configuration/LinkedUsers/columns.tsx +++ b/apps/client-ts/src/components/Configuration/LinkedUsers/columns.tsx @@ -2,6 +2,40 @@ import { ColumnDef } from "@tanstack/react-table"; import { ColumnLU } from "./schema"; import { DataTableColumnHeader } from "@/components/shared/data-table-column-header"; import { Badge } from "@/components/ui/badge"; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; +import { toast } from "sonner"; + + +const LinkedUserIdComponent = ({ row } : {row:any}) => { + + const handleCopyLinkedUserId = () => { + navigator.clipboard.writeText(row.getValue("linked_user_id")); + toast.success("LinkedUser ID copied!", { + action: { + label: "Close", + onClick: () => console.log("Close"), + }, + }) + }; + + return ( +
+ + + + + {row.getValue("linked_user_id")} + + + +

Copy

+
+
+
+
+ ) +} + export const columns: ColumnDef[] = [ { @@ -9,13 +43,7 @@ export const columns: ColumnDef[] = [ header: ({ column }) => ( ), - cell: ({ row }) =>{ - return ( -
- {row.getValue("linked_user_id")} -
- ) - }, + cell: LinkedUserIdComponent, enableSorting: false, enableHiding: false, }, diff --git a/apps/client-ts/src/components/Nav/main-nav-sm.tsx b/apps/client-ts/src/components/Nav/main-nav-sm.tsx index 9c643738a..390c13f94 100644 --- a/apps/client-ts/src/components/Nav/main-nav-sm.tsx +++ b/apps/client-ts/src/components/Nav/main-nav-sm.tsx @@ -61,7 +61,6 @@ export function SmallNav({ setOpen(false)}> - {/* Panora. */} {theme == "light" ? : } @@ -70,18 +69,6 @@ export function SmallNav({
*/}