Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🔥 Fixed hook state in the webapp when project was null #566

Merged
merged 2 commits into from
Jul 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/rare-garlics-flow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@panora/embedded-card-react": patch
"@panora/frontend-sdk": patch
---

Readme patch
39 changes: 27 additions & 12 deletions apps/embedded-catalog/react/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,36 +21,51 @@ or
yarn add @panora/embedded-card-react
```

## Import the component
## Import the components

```bash
# Import the css file
```ts
import "@panora/embedded-card-react/dist/index.css";

import PanoraProviderCard from "@panora/embedded-card-react";
import { PanoraDynamicCatalogCard, PanoraProviderCard } from '@panora/embedded-card-react';
```

## Use the component

- The `optionalApiUrl` is an optional prop to use the component with the self-hosted version of Panora.

```bash
```ts
<PanoraProviderCard
name={"hubspot"} // name of the provider
projectId={"c9a1b1f8-466d-442d-a95e-11cdd00baf49"} // the project id tied to your organization
returnUrl={"https://acme.inc"} // the url you want to redirect users to after the connection flow is successful
linkedUserId={"b860d6c1-28f9-485c-86cd-fb09e60f10a2"} // the linked id of the user if already created in Panora system or user's info in your system
optionalApiUrl={"http://localhost:3000"} // Only add this prop to use the component with a self-hosted version of Panora. Without this prop, the component uses the cloud version of Panora.
projectId={"c9a1b1f8-466d-442d-a95e-11cdd00baf49"} // Copy it from your dahshboard
linkedUserId={"b860d6c1-28f9-485c-86cd-fb09e60f10a2"} // You can copy it from your Panora dahsbord under /configuration tab
optionalApiUrl={"http://localhost:3000"} // Optional (only if you are in selfhost mode and want to use localhost:3000), by default: api.panora.dev
/>

<PanoraDynamicCatalogCard
category={ConnectorCategory.Crm}
projectId={"f9e9601e-d6da-471a-9777-94257e9b4f00"}
linkedUserId={"4c6ca51b-7b23-4e3a-9309-24d2d331a04d"}
optionalApiUrl="http://localhost:3000"
/>
```

```ts
These are the types needed for the component.
These are the types needed for the components.

The `<PanoraProviderCard />` takes this props type:

interface ProviderCardProp {
name: string;
projectId: string;
returnUrl: string;
linkedUserIdOrRemoteUserInfo: string;
linkedUserId: string;
}

The `<PanoraDynamicCatalogCard />` takes this props type:

interface DynamicCardProp {
projectId: string;
linkedUserId: string;
category?: ConnectorCategory;
optionalApiUrl?: string,
}
```
75 changes: 75 additions & 0 deletions apps/frontend-sdk/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@

## Frontend SDK (React)

It is a React component aimed to be used in any of your pages so end-users can connect their 3rd parties in 1-click!

## Installation

```bash
npm i @panora/frontend-sdk
```

or

```bash
pnpm i @panora/frontend-sdk
```

or

```bash
yarn add @panora/frontend-sdk
```

## Use the component

```ts
import { ConnectorCategory } from '@panora/shared'
import Panora from '@panora/frontend-sdk'

const panora = new Panora({ apiKey: 'YOUR_PRIVATE_API_KEY' });

// kickstart the connection (OAuth, ApiKey, Basic)
panora.connect({
providerName: "hubspot",
vertical: ConnectorCategory.Crm,
linkedUserId: "4c6ca51b-7b23-4e3a-9309-24d2d331a04d",
})
```

```ts
The Panora SDK must be instantiated with this type:

interface PanoraConfig {
apiKey: string;
overrideApiUrl: string;
// Optional (only if you are in selfhost mode and want to use localhost:3000), by default: api.panora.dev
}

The .connect() function takes this type:

interface ConnectOptions {
providerName: string;
vertical: ConnectorCategory; // Must be imported from @panora/shared
linkedUserId: string; // You can copy it from your Panora dahsbord under /configuration tab
credentials?: Credentials; // Optional if you try to use OAuth
options?: {
onSuccess?: () => void;
onError?: (error: Error) => void;
overrideReturnUrl?: string;
}
}

By default, for OAuth we use Panora managed OAuth apps but if we dont have one registered OR you want to use your own, you must register that under /configuration tab from the webapp and it will automatically use these custom credentials !

interface Credentials {
username?: string; // Used for Basic Auth
password?: string; // Used for Basic Auth
apiKey?: string; // Used for Api Key Auth
}

For Basic Auth some providers may only ask for username or password.

In this case just specify either password or username depending on the 3rd party reference.

```
4 changes: 2 additions & 2 deletions apps/frontend-sdk/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ interface ConnectOptions {
vertical: ConnectorCategory;
linkedUserId: string;
credentials?: Credentials;
options: {
options?: {
onSuccess?: () => void;
onError?: (error: Error) => void;
overrideReturnUrl?: string;
Expand Down Expand Up @@ -65,7 +65,7 @@ class Panora {
}

async connect(options: ConnectOptions): Promise<Window | null> {
const { providerName, vertical, linkedUserId, credentials, options: {onSuccess, onError, overrideReturnUrl} } = options;
const { providerName, vertical, linkedUserId, credentials, options: {onSuccess, onError, overrideReturnUrl} = {} } = options;

try {
const projectId = await this.fetchProjectId();
Expand Down
22 changes: 14 additions & 8 deletions apps/magic-link/src/hooks/queries/useProjectConnectors.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
import { useQuery } from '@tanstack/react-query';
import config from '@/helpers/config';

const useProjectConnectors = (id: string) => {
const useProjectConnectors = (id: string | null) => {
return useQuery({
queryKey: ['project-connectors', id],
queryKey: ['project-connectors', id],
queryFn: async (): Promise<any> => {
if (!id) {
throw new Error('Project ID is not available');
}
const response = await fetch(`${config.API_URL}/project-connectors?projectId=${id}`);
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
}
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
},
enabled: !!id, // Only run the query if id is truthy
retry: false, // Don't retry if the project ID is not available
});
};
export default useProjectConnectors;

export default useProjectConnectors;
16 changes: 11 additions & 5 deletions apps/magic-link/src/hooks/queries/useUniqueMagicLink.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,22 @@ import config from '@/helpers/config';

type Mlink = MagicLink & {id_project: string}

const useUniqueMagicLink = (id: string) => {
return useQuery({
queryKey: ['magic-link', id],
const useUniqueMagicLink = (id: string | null) => {
return useQuery<Mlink, Error>({
queryKey: ['magic-link', id],
queryFn: async (): Promise<Mlink> => {
if (!id) {
throw new Error('Magic Link ID is not available');
}
const response = await fetch(`${config.API_URL}/magic-links/single?id=${id.trim()}`);
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
}
},
enabled: !!id && id.trim().length > 0, // Only run the query if id is truthy and not just whitespace
retry: false, // Don't retry if the magic link ID is not available
});
};
export default useUniqueMagicLink;

export default useUniqueMagicLink;
37 changes: 19 additions & 18 deletions apps/magic-link/src/lib/ProviderModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
const [openApiKeyDialog,setOpenApiKeyDialog] = useState<boolean>(false);
const [projectId, setProjectId] = useState<string>("");
const [data, setData] = useState<Provider[]>([]);
const [isProjectIdReady, setIsProjectIdReady] = useState(false);
const [errorResponse,setErrorResponse] = useState<{
errorPresent: boolean; errorMessage : string
}>({errorPresent:false,errorMessage:''})
Expand All @@ -58,14 +59,14 @@
status: boolean; provider: string
}>({status: false, provider: ''});

const [uniqueMagicLinkId, setUniqueMagicLinkId] = useState<string>('');
const [uniqueMagicLinkId, setUniqueMagicLinkId] = useState<string | null>(null);
const [openSuccessDialog,setOpenSuccessDialog] = useState<boolean>(false);
const [currentProviderLogoURL,setCurrentProviderLogoURL] = useState<string>('')
const [currentProvider,setCurrentProvider] = useState<string>('')

const {mutate : createApiKeyConnection} = useCreateApiKeyConnection();
const {data: magicLink} = useUniqueMagicLink(uniqueMagicLinkId);
const {data: connectorsForProject} = useProjectConnectors(projectId);
const {data: connectorsForProject} = useProjectConnectors(isProjectIdReady ? projectId : null);

// const form = useForm<z.infer<typeof formSchema>>({
// resolver: zodResolver(formSchema),
Expand All @@ -88,28 +89,28 @@
useEffect(() => {
if (magicLink) {
setProjectId(magicLink?.id_project);
setIsProjectIdReady(true);
}
}, [magicLink]);


useEffect(()=>{
const PROVIDERS = selectedCategory == "All" ? providersArray() : providersArray(selectedCategory);
const getConnectorsToDisplay = () => {
// First, check if the company selected custom connectors in the UI or not
const unwanted_connectors = transformConnectorsStatus(connectorsForProject).filter(connector => connector.status === "false");
// Filter out the providers present in the unwanted connectors array
const filteredProviders = PROVIDERS.filter(provider => {
return !unwanted_connectors.some( (unwanted) =>
unwanted.category === provider.vertical && unwanted.connector_name === provider.name
);
});
return filteredProviders;
}

if(connectorsForProject) {
setData(getConnectorsToDisplay())
if (isProjectIdReady && connectorsForProject) {
const PROVIDERS = selectedCategory == "All" ? providersArray() : providersArray(selectedCategory);
const getConnectorsToDisplay = () => {
// First, check if the company selected custom connectors in the UI or not
const unwanted_connectors = transformConnectorsStatus(connectorsForProject).filter(connector => connector.status === "false");
// Filter out the providers present in the unwanted connectors array
const filteredProviders = PROVIDERS.filter(provider => {
return !unwanted_connectors.some( (unwanted) =>
unwanted.category === provider.vertical && unwanted.connector_name === provider.name
);
});
return filteredProviders;
}
setData(getConnectorsToDisplay())
}
}, [connectorsForProject, selectedCategory])
}, [connectorsForProject, selectedCategory, isProjectIdReady])

const { open, isReady } = useOAuth({
providerName: selectedProvider?.provider!,
Expand Down Expand Up @@ -159,7 +160,7 @@
provider: ''
});
}
}, [startFlow, isReady]);

Check warning on line 163 in apps/magic-link/src/lib/ProviderModal.tsx

View workflow job for this annotation

GitHub Actions / Build and Test (18.x)

React Hook useEffect has a missing dependency: 'open'. Either include it or remove the dependency array



Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export function DataTableRowActions<TData>({

const {deleteApiKeyPromise} = useDeleteApiKey();
const {deleteWebhookPromise} = useDeleteWebhook();

const queryClient = useQueryClient();

const handleDeletion = () => {
Expand Down
Binary file added docs/images/custom-connectors-widget.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/embed-video.mp4
Binary file not shown.
Binary file added docs/images/frontend-sdk-video.mp4
Binary file not shown.
3 changes: 2 additions & 1 deletion docs/mint.json
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,7 @@
{
"group": "Recipes",
"pages": [
"recipes/frontend-sdk",
"recipes/embed-catalog",
"recipes/import-existing-users",
"recipes/catch-connection-token",
Expand All @@ -433,7 +434,7 @@
"group": "Devtools",
"pages": [
{
"group": "SDKs",
"group": "Backend SDKs",
"pages": ["backend-sdk/typescript", "backend-sdk/python"]
}
]
Expand Down
36 changes: 24 additions & 12 deletions docs/recipes/embed-catalog.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ icon: "square-terminal"

<CodeGroup>
```shell React
npm i @panora/embedded-card-react
pnpm i @panora/embedded-card-react
```
</CodeGroup>

Expand All @@ -20,6 +20,15 @@ icon: "square-terminal"
[here](/glossary/metadata/category).
</Note>

<Frame type="glass">
<video
controls
className="block"
src="/images/embed-video.mp4"
alt="Hero Light"
/>
</Frame>

#### Import a single connector card

You must import both `@panora/shared` (which contains the types) and `@panora/embedded-card-react`.
Expand All @@ -34,12 +43,12 @@ Don't forget to import the styles as well `import "@panora/embedded-card-react/d

const MyPage = () => {
return (
<PanoraProviderCard
name={"hubspot"} // name of the provider
category={ConnectorCategory.Crm} // category where provider belongs to
projectId={"c9a1b1f8-466d-442d-a95e-11cdd00baf49"} // the project id tied to your organization
returnUrl={"https://acme.inc"} // the url you want to redirect users to after the connection flow is successful
linkedUserId={"b860d6c1-28f9-485c-86cd-fb09e60f10a2"} // the linked id of the user if already created in Panora system or user's info in your system
<PanoraProviderCard
name={"hubspot"} // name of the provider
category={ConnectorCategory.Crm}
projectId={"f9e9601e-d6da-471a-9777-94257e9b4f00"}
linkedUserId={"4c6ca51b-7b23-4e3a-9309-24d2d331a04d"}
optionalApiUrl={"http://localhost:3000"} // Optional (only if you are in selfhost mode and want to use localhost:3000), by default: api.panora.dev
/>
)
}
Expand All @@ -55,10 +64,10 @@ Don't forget to import the styles as well `import "@panora/embedded-card-react/d
const MyPage = () => {
return (
<PanoraDynamicCatalogCard
category={ConnectorCategory.Crm} // optional but if provided it returns only connectors of the category
projectId={"c9a1b1f8-466d-442d-a95e-11cdd00baf49"} // the project id tied to your organization
returnUrl={"https://acme.inc"} // the url you want to redirect users to after the connection flow is successful
linkedUserId={"b860d6c1-28f9-485c-86cd-fb09e60f10a2"} // the linked id of the user if already created in Panora system or user's info in your system
category={ConnectorCategory.Crm}
projectId={"f9e9601e-d6da-471a-9777-94257e9b4f00"}
linkedUserId={"4c6ca51b-7b23-4e3a-9309-24d2d331a04d"}
optionalApiUrl="http://localhost:3000"
/>
)
}
Expand All @@ -67,9 +76,12 @@ Don't forget to import the styles as well `import "@panora/embedded-card-react/d

Congrats ! You should be able to see the displayed connectors !


# Step 2 (optional): Select your own connectors

**insert**video
<Frame type="glass">
<img src="/images/custom-connectors-widget.png" />
</Frame>

You have the ability to choose which connectors will be rendered (also applied to magic-link).

Expand Down
Loading
Loading