diff --git a/apps/client-ts/package.json b/apps/client-ts/package.json index 501a16ecd..362861178 100644 --- a/apps/client-ts/package.json +++ b/apps/client-ts/package.json @@ -20,6 +20,7 @@ "@radix-ui/react-label": "^2.0.2", "@radix-ui/react-navigation-menu": "^1.1.4", "@radix-ui/react-popover": "^1.0.7", + "@radix-ui/react-progress": "^1.0.3", "@radix-ui/react-scroll-area": "^1.0.5", "@radix-ui/react-select": "^2.0.0", "@radix-ui/react-separator": "^1.0.3", @@ -48,12 +49,14 @@ "react": "^18", "react-day-picker": "^8.10.0", "react-dom": "^18", + "react-dropzone": "^14.2.3", "react-hook-form": "^7.51.2", "react-resizable-panels": "^2.0.19", "recharts": "^2.10.1", "sonner": "^1.4.3", "tailwind-merge": "^2.2.1", "tailwindcss-animate": "^1.0.7", + "xlsx": "^0.18.5", "zod": "^3.22.4", "zustand": "^4.4.7" }, diff --git a/apps/client-ts/src/app/(Dashboard)/api-keys/page.tsx b/apps/client-ts/src/app/(Dashboard)/api-keys/page.tsx index def0960d4..dc0480787 100644 --- a/apps/client-ts/src/app/(Dashboard)/api-keys/page.tsx +++ b/apps/client-ts/src/app/(Dashboard)/api-keys/page.tsx @@ -1,6 +1,5 @@ 'use client' -import { PlusCircledIcon } from "@radix-ui/react-icons"; import { Button } from "@/components/ui/button" import { DataTable } from "@/components/shared/data-table"; import { @@ -26,7 +25,6 @@ import useProjectStore from "@/state/projectStore"; import useCreateApiKey from "@/hooks/create/useCreateApiKey"; import useProfileStore from "@/state/profileStore"; import { Suspense, useEffect, useState } from "react"; -import { cn } from "@/lib/utils"; import { usePostHog } from 'posthog-js/react' import config from "@/lib/config"; import * as z from "zod" @@ -36,13 +34,15 @@ import { DataTableLoading } from "@/components/shared/data-table-loading"; import {CustomHeading} from "@/components/shared/custom-heading"; import { useColumns } from "@/components/ApiKeys/columns"; import { PlusCircle } from "lucide-react"; +import { toast } from "sonner"; +import { Badge } from "@/components/ui/badge"; +import { useQueryClient } from "@tanstack/react-query"; const formSchema = z.object({ apiKeyIdentifier: z.string().min(2, { message: "apiKeyIdentifier must be at least 2 characters.", }) }) - interface TSApiKeys { id_api_key: string; name : string; @@ -53,11 +53,12 @@ export default function Page() { const [open,setOpen] = useState(false) const [tsApiKeys,setTSApiKeys] = useState([]) + const queryClient = useQueryClient(); + const {idProject} = useProjectStore(); const {profile} = useProfileStore(); - + const { createApiKeyPromise } = useCreateApiKey(); const { data: apiKeys, isLoading, error } = useApiKeys(); - const { mutate } = useCreateApiKey(); const columns = useColumns(); useEffect(() => { @@ -94,11 +95,33 @@ export default function Page() { const onSubmit = (values: z.infer) => { - mutate({ - userId: profile!.id_user, - projectId: idProject, - keyName: values.apiKeyIdentifier + toast.promise( + createApiKeyPromise({ + userId: profile!.id_user, + projectId: idProject, + keyName: values.apiKeyIdentifier + }), + { + loading: 'Loading...', + success: (data: any) => { + queryClient.setQueryData(['api-keys'], (oldQueryData = []) => { + return [...oldQueryData, data]; + }); + return ( +
+ +
+ Api key + {`${data.name}`} + has been created +
+
+ ) + ; + }, + error: 'Error', }); + posthog?.capture('api_key_created', { id_project: idProject, mode: config.DISTRIBUTION diff --git a/apps/client-ts/src/app/(Dashboard)/b2c/profile/page.tsx b/apps/client-ts/src/app/(Dashboard)/b2c/profile/page.tsx index e902452a0..6fddc0616 100644 --- a/apps/client-ts/src/app/(Dashboard)/b2c/profile/page.tsx +++ b/apps/client-ts/src/app/(Dashboard)/b2c/profile/page.tsx @@ -16,6 +16,7 @@ import useProfileStore from "@/state/profileStore"; import useProjectStore from "@/state/projectStore" import { useQueryClient } from '@tanstack/react-query'; import { useState } from "react"; +import { toast } from "sonner"; const Profile = () => { @@ -30,6 +31,12 @@ const Profile = () => { try { await navigator.clipboard.writeText(email) setCopied(true); + toast.success("Email copied", { + action: { + label: "Close", + onClick: () => console.log("Close"), + }, + }) setTimeout(() => setCopied(false), 2000); // Reset copied state after 2 seconds } catch (err) { console.error('Failed to copy: ', err); diff --git a/apps/client-ts/src/app/(Dashboard)/configuration/page.tsx b/apps/client-ts/src/app/(Dashboard)/configuration/page.tsx index 33066589c..cdcb55516 100644 --- a/apps/client-ts/src/app/(Dashboard)/configuration/page.tsx +++ b/apps/client-ts/src/app/(Dashboard)/configuration/page.tsx @@ -18,7 +18,6 @@ import FieldMappingsTable from "@/components/Configuration/FieldMappings/FieldMa import AddLinkedAccount from "@/components/Configuration/LinkedUsers/AddLinkedAccount"; import useLinkedUsers from "@/hooks/get/useLinkedUsers"; import useFieldMappings from "@/hooks/get/useFieldMappings"; -import { Skeleton } from "@/components/ui/skeleton"; import { useState } from "react"; import AddWebhook from "@/components/Configuration/Webhooks/AddWebhook"; import { WebhooksPage } from "@/components/Configuration/Webhooks/WebhooksPage"; @@ -33,6 +32,7 @@ import { Button } from "@/components/ui/button"; import { LoadingSpinner } from "@/components/ui/loading-spinner"; import { CatalogWidget } from "@/components/Configuration/Catalog/CatalogWidget"; import { CopySnippet } from "@/components/Configuration/Catalog/CopySnippet"; +import {Button as Button2} from "@/components/ui/button2" export default function Page() { @@ -191,8 +191,10 @@ export default function Page() { - You built {mappings ? mappings.length : } fields mappings. + You built {mappings ? mappings.length : } fields mappings. + Learn more about custom field mappings in our docs ! + @@ -210,8 +212,10 @@ export default function Page() { Your Webhooks - You enabled {webhooks ? webhooks.length : } webhooks. - Read more about webhooks from our documentation + You enabled {webhooks ? webhooks.length : } webhooks. + + Read more about webhooks from our documentation + diff --git a/apps/client-ts/src/components/ApiKeys/columns.tsx b/apps/client-ts/src/components/ApiKeys/columns.tsx index 00183c51d..067bd7943 100644 --- a/apps/client-ts/src/components/ApiKeys/columns.tsx +++ b/apps/client-ts/src/components/ApiKeys/columns.tsx @@ -9,6 +9,8 @@ import { PasswordInput } from "../ui/password-input" import { useState } from "react" import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../ui/tooltip" import { Button } from "../ui/button" +import { toast } from "sonner" +import { Card } from "antd" export function useColumns() { const [copiedState, setCopiedState] = useState<{ [key: string]: boolean }>({}); @@ -19,6 +21,12 @@ export function useColumns() { ...prevState, [token]: true, })); + toast.success("Api key copied", { + action: { + label: "Close", + onClick: () => console.log("Close"), + }, + }) setTimeout(() => { setCopiedState((prevState) => ({ ...prevState, diff --git a/apps/client-ts/src/components/Auth/CustomLoginComponent/CreateUserForm.tsx b/apps/client-ts/src/components/Auth/CustomLoginComponent/CreateUserForm.tsx index c8705d92a..2b96b4427 100644 --- a/apps/client-ts/src/components/Auth/CustomLoginComponent/CreateUserForm.tsx +++ b/apps/client-ts/src/components/Auth/CustomLoginComponent/CreateUserForm.tsx @@ -10,7 +10,6 @@ import { } from "@/components/ui/card" import { Input } from "@/components/ui/input" import { Button } from "@/components/ui/button" -import { Label } from "@/components/ui/label" import * as z from "zod" import { Form, @@ -24,6 +23,9 @@ import { zodResolver } from "@hookform/resolvers/zod" import { useForm } from "react-hook-form" import { PasswordInput } from '@/components/ui/password-input' import useCreateUser from '@/hooks/create/useCreateUser' +import { toast } from 'sonner' +import { Badge } from '@/components/ui/badge' +import { useQueryClient } from '@tanstack/react-query' const formSchema = z.object({ first_name: z.string().min(2,{ @@ -42,8 +44,8 @@ const formSchema = z.object({ }) const CreateUserForm = () => { - - const {mutate : createUserMutate} = useCreateUser(); + const {createUserPromise} = useCreateUser(); + const queryClient = useQueryClient(); const form = useForm>({ resolver: zodResolver(formSchema), @@ -56,109 +58,122 @@ const CreateUserForm = () => { }) const onSubmit = (values: z.infer) => { - // console.log(values) - createUserMutate({ - first_name:values.first_name, - last_name:values.last_name, - email:values.email, - strategy:'b2c', - password_hash:values.password - }, - { - onSuccess:() => { - form.reset(); - } - }); - - - } - + toast.promise( + createUserPromise({ + first_name:values.first_name, + last_name:values.last_name, + email:values.email, + strategy:'b2c', + password_hash:values.password + }), + { + loading: 'Loading...', + success: (data: any) => { + form.reset(); + queryClient.setQueryData(['users'], (oldQueryData = []) => { + return [...oldQueryData, data]; + }); + return ( +
+ +
+ User + {`${data.email}`} + has been created +
+
+ ) + ; + }, + error: 'Error', + }); + }; - return ( - <> -
- - - - Sign Up - - Create your account. - - - -
-
+ return ( + <> + + + + + Sign Up + + Create your account. + + + +
+
+
+ ( + + First Name + + + + + + )} + /> +
+
+ ( + + Last Name + + + + + + )} + /> +
+
- ( - - First Name - - - - - - )} - /> + ( + + Email + + + + + + )} + />
- ( - - Last Name - - - - - - )} - /> + ( + + Password + + + + + + )} + />
-
- ( - - Email - - - - - - )} - /> -
-
- ( - - Password - - - - - - )} - /> -
-
- - - - - - - - - ) + + + + + + + + + ) } export default CreateUserForm \ No newline at end of file diff --git a/apps/client-ts/src/components/Auth/CustomLoginComponent/LoginUserForm.tsx b/apps/client-ts/src/components/Auth/CustomLoginComponent/LoginUserForm.tsx index 076257337..9093f6f10 100644 --- a/apps/client-ts/src/components/Auth/CustomLoginComponent/LoginUserForm.tsx +++ b/apps/client-ts/src/components/Auth/CustomLoginComponent/LoginUserForm.tsx @@ -24,6 +24,11 @@ import { zodResolver } from "@hookform/resolvers/zod" import { useForm } from "react-hook-form" import useCreateLogin from '@/hooks/create/useCreateLogin' import { useRouter } from "next/navigation"; +import { Badge } from '@/components/ui/badge' +import { toast } from 'sonner' +import useProfileStore from '@/state/profileStore'; +import Cookies from 'js-cookie'; +import { useQueryClient } from '@tanstack/react-query' const formSchema = z.object({ email: z.string().email({ @@ -37,8 +42,9 @@ const formSchema = z.object({ const LoginUserForm = () => { const router = useRouter() - - const {mutate : loginMutate} = useCreateLogin() + const queryClient = useQueryClient(); + const {loginPromise} = useCreateLogin() + const {setProfile}= useProfileStore(); const form = useForm>({ resolver: zodResolver(formSchema), @@ -48,18 +54,36 @@ const LoginUserForm = () => { }, }) -const onSubmit = (values: z.infer) => { - loginMutate({ - email:values.email, - password_hash:values.password - }, - { - onSuccess: () => router.replace("/connections") - }) - -} - - + const onSubmit = (values: z.infer) => { + toast.promise( + loginPromise({ + email:values.email, + password_hash:values.password + }), + { + loading: 'Loading...', + success: (data: any) => { + setProfile(data.user); + Cookies.set('access_token',data.access_token,{expires:1}); + router.replace("/connections"); + queryClient.setQueryData(['users'], (oldQueryData = []) => { + return [...oldQueryData, data]; + }); + return ( +
+ +
+ User + {`${data.email}`} + has been logged in +
+
+ ) + ; + }, + error: 'Error', + }); + }; return ( <> @@ -107,7 +131,7 @@ const onSubmit = (values: z.infer) => { - + diff --git a/apps/client-ts/src/components/Configuration/Catalog/CatalogWidget.tsx b/apps/client-ts/src/components/Configuration/Catalog/CatalogWidget.tsx index 6d17b6062..edad14f74 100644 --- a/apps/client-ts/src/components/Configuration/Catalog/CatalogWidget.tsx +++ b/apps/client-ts/src/components/Configuration/Catalog/CatalogWidget.tsx @@ -1,26 +1,80 @@ -import { ComponentProps, useState } from "react" +import { ComponentProps, useEffect, useState } from "react" import { cn } from "@/lib/utils" import { Badge } from "@/components/ui/badge" import { ScrollArea } from "@/components/ui/scroll-area" -import { AuthStrategy, categoriesVerticals, Provider, providersArray } from "@panora/shared" +import { AuthStrategy, categoriesVerticals, ConnectorCategory, providersArray, slugFromCategory } from "@panora/shared" import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from "@/components/ui/dropdown-menu" import { Button } from "@/components/ui/button" import { ListFilter } from "lucide-react" import { Card } from "@/components/ui/card" import { Switch } from "@/components/ui/switch" -import { Label } from "@/components/ui/label" +import useProjectStore from "@/state/projectStore" +import useUpdateProjectConnectors from "@/hooks/update/useUpdateProjectConnectors" +import useProjectConnectors from "@/hooks/get/useProjectConnectors" +import { toast } from "sonner" export const verticals = categoriesVerticals as string[]; export function CatalogWidget() { const [vertical, setVertical] = useState("All") + const [connectorStatuses, setConnectorStatuses] = useState<{ [key: string]: boolean }>({}); + const filteredConnectors = vertical === "All" ? providersArray() : providersArray(vertical); + const {idProject} = useProjectStore(); + + const {data} = useProjectConnectors(idProject); + + const {updateProjectConnectorPromise} = useUpdateProjectConnectors(); + + useEffect(() => { + if (data) { + const filteredData = Object.entries(data).reduce((acc, [key, value]) => { + if (key !== 'id_project' && key !== 'id_project_connector') { + acc[key] = Boolean(value); + } + return acc; + }, {} as { [key: string]: boolean }); + + setConnectorStatuses(filteredData); + } + }, [data]); + const handleCheckboxChange = (vertical: string) => { setVertical(vertical); }; + + const handleSwitchChange = async (providerKey: string, currentStatus: boolean) => { + const newStatus = !currentStatus; + + setConnectorStatuses(prev => ({ + ...prev, + [providerKey]: newStatus + })); + toast.promise( + updateProjectConnectorPromise({ + column: providerKey, + status: newStatus + }), + { + loading: 'Loading...', + success: (data: any) => { + return ( +
+ +
+ Changes have been saved +
+
+ ) + ; + }, + error: 'Error', + }); + }; + return ( <>
@@ -60,60 +114,56 @@ export function CatalogWidget() {
- - -
- {filteredConnectors.map((item) => ( - -
-
-
- -
{`${item.name.substring(0, 1).toUpperCase()}${item.name.substring(1)}`}
-
-
- {item.description!.substring(0, 300)} -
-
- {item.vertical && - - {item.vertical} - - } - {item.authStrategy && - - {item.authStrategy} - - } + {filteredConnectors.map((item) => { + const providerKey = `${slugFromCategory(item.vertical! as ConnectorCategory)}_${item.name}`; + const isChecked = connectorStatuses[providerKey] ?? true; // Default to true if undefined + + return ( + +
+
+
+ +
{`${item.name.substring(0, 1).toUpperCase()}${item.name.substring(1)}`}
+
+
+ {item.description!.substring(0, 300)} +
+
+ {item.vertical && + + {item.vertical} + + } + {item.authStrategy && + + {item.authStrategy} + + } +
-
-
+
disableWebhook(row.original.id_webhook_endpoint, !row.getValue('active')) } + checked={isChecked} + onCheckedChange={() => handleSwitchChange(providerKey, isChecked)} /> +
-
- - - ))} + + )}) + }
diff --git a/apps/client-ts/src/components/Configuration/Catalog/CopySnippet.tsx b/apps/client-ts/src/components/Configuration/Catalog/CopySnippet.tsx index 084b9d49a..879cf906f 100644 --- a/apps/client-ts/src/components/Configuration/Catalog/CopySnippet.tsx +++ b/apps/client-ts/src/components/Configuration/Catalog/CopySnippet.tsx @@ -12,6 +12,7 @@ import { import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; import { Copy } from "lucide-react"; import { useState } from "react"; +import { toast } from "sonner"; export const CopySnippet = () => { const [open,setOpen] = useState(false); @@ -30,6 +31,12 @@ export const CopySnippet = () => { linkedUserId={'b860d6c1-28f9-485c-86cd-fb09e60f10a2'} ` ); + toast.success("Code snippet copied", { + action: { + label: "Close", + onClick: () => console.log("Close"), + }, + }) setCopiedLeft(true); setTimeout(() => { setCopiedLeft(false); @@ -46,6 +53,12 @@ export const CopySnippet = () => { linkedUserId={'b860d6c1-28f9-485c-86cd-fb09e60f10a2'} ` ); + toast.success("Code snippet copied", { + action: { + label: "Close", + onClick: () => console.log("Close"), + }, + }) setCopiedRight(true); setTimeout(() => { setCopiedRight(false); diff --git a/apps/client-ts/src/components/Configuration/Connector/ConnectorDisplay.tsx b/apps/client-ts/src/components/Configuration/Connector/ConnectorDisplay.tsx index 440497963..3f39d905c 100644 --- a/apps/client-ts/src/components/Configuration/Connector/ConnectorDisplay.tsx +++ b/apps/client-ts/src/components/Configuration/Connector/ConnectorDisplay.tsx @@ -18,6 +18,8 @@ import { DataTableFacetedFilter } from "@/components/shared/data-table-faceted-f import useCreateConnectionStrategy from "@/hooks/create/useCreateConnectionStrategy" import useUpdateConnectionStrategy from "@/hooks/update/useUpdateConnectionStrategy" import useConnectionStrategyAuthCredentials from "@/hooks/get/useConnectionStrategyAuthCredentials" +import { useQueryClient } from "@tanstack/react-query" +import { toast } from "sonner" interface ItemDisplayProps { item?: Provider @@ -49,10 +51,11 @@ export function ConnectorDisplay({ item }: ItemDisplayProps) { const [switchEnabled, setSwitchEnabled] = useState(false); const { idProject } = useProjectStore() const { data: connectionStrategies, isLoading: isConnectionStrategiesLoading, error: isConnectionStategiesError } = useConnectionStrategies() - const { mutate: createCS } = useCreateConnectionStrategy(); - const { mutate: updateCS } = useUpdateConnectionStrategy() + const { createCsPromise } = useCreateConnectionStrategy(); + const { updateCsPromise } = useUpdateConnectionStrategy() const { mutateAsync: fetchCredentials, data: fetchedData } = useConnectionStrategyAuthCredentials(); - + const queryClient = useQueryClient(); + const posthog = usePostHog() const mappingConnectionStrategies = connectionStrategies?.filter((cs) => extractVertical(cs.type).toLowerCase() == item?.vertical && extractProvider(cs.type).toLowerCase() == item?.name) @@ -72,6 +75,12 @@ export function ConnectorDisplay({ item }: ItemDisplayProps) { try { await navigator.clipboard.writeText(`${config.API_URL}/connections/oauth/callback`) setCopied(true); + toast.success("Redirect uri copied", { + action: { + label: "Close", + onClick: () => console.log("Close"), + }, + }) setTimeout(() => setCopied(false), 2000); // Reset copied state after 2 seconds } catch (err) { console.error('Failed to copy: ', err); @@ -97,22 +106,60 @@ export function ConnectorDisplay({ item }: ItemDisplayProps) { } if (performUpdate) { const dataToUpdate = mappingConnectionStrategies[0]; - updateCS({ - id_cs: dataToUpdate.id_connection_strategy, - updateToggle: false, - status: dataToUpdate.status, - attributes: ["client_id", "client_secret", "scope"], - values: [client_id, client_secret, scope] - }) + toast.promise( + updateCsPromise({ + id_cs: dataToUpdate.id_connection_strategy, + updateToggle: false, + status: dataToUpdate.status, + attributes: ["client_id", "client_secret", "scope"], + values: [client_id, client_secret, scope] + }), + { + loading: 'Loading...', + success: (data: any) => { + queryClient.setQueryData(['connection-strategies'], (oldQueryData = []) => { + return oldQueryData.map((CS) => CS.id_connection_strategy === data.id_connection_strategy ? data : CS) + }); + return ( +
+ +
+ Changes have been saved +
+
+ ) + ; + }, + error: 'Error', + }); posthog?.capture("Connection_strategy_OAuth2_updated", { id_project: idProject, mode: config.DISTRIBUTION }); } else { - createCS({ - type: providerToType(item?.name, item?.vertical!, AuthStrategy.oauth2), - attributes: ["client_id", "client_secret", "scope"], - values: [client_id, client_secret, scope] + toast.promise( + createCsPromise({ + type: providerToType(item?.name, item?.vertical!, AuthStrategy.oauth2), + attributes: ["client_id", "client_secret", "scope"], + values: [client_id, client_secret, scope] + }), + { + loading: 'Loading...', + success: (data: any) => { + queryClient.setQueryData(['connections-strategies'], (oldQueryData = []) => { + return [...oldQueryData, data]; + }); + return ( +
+ +
+ Changes have been saved +
+
+ ) + ; + }, + error: 'Error', }); posthog?.capture("Connection_strategy_OAuth2_created", { id_project: idProject, @@ -130,22 +177,60 @@ export function ConnectorDisplay({ item }: ItemDisplayProps) { } if (performUpdate) { const dataToUpdate = mappingConnectionStrategies[0]; - updateCS({ - id_cs: dataToUpdate.id_connection_strategy, - updateToggle: false, - status: dataToUpdate.status, - attributes: ["api_key"], - values: [api_key] - }) + toast.promise( + updateCsPromise({ + id_cs: dataToUpdate.id_connection_strategy, + updateToggle: false, + status: dataToUpdate.status, + attributes: ["api_key"], + values: [api_key] + }), + { + loading: 'Loading...', + success: (data: any) => { + queryClient.setQueryData(['connection-strategies'], (oldQueryData = []) => { + return oldQueryData.map((CS) => CS.id_connection_strategy === data.id_connection_strategy ? data : CS) + }); + return ( +
+ +
+ Changes have been saved +
+
+ ) + ; + }, + error: 'Error', + }); posthog?.capture("Connection_strategy_API_KEY_updated", { id_project: idProject, mode: config.DISTRIBUTION }); } else { - createCS({ - type: providerToType(item?.name, item?.vertical!, AuthStrategy.api_key), - attributes: ["api_key"], - values: [api_key] + toast.promise( + createCsPromise({ + type: providerToType(item?.name, item?.vertical!, AuthStrategy.api_key), + attributes: ["api_key"], + values: [api_key] + }), + { + loading: 'Loading...', + success: (data: any) => { + queryClient.setQueryData(['connections-strategies'], (oldQueryData = []) => { + return [...oldQueryData, data]; + }); + return ( +
+ +
+ Changes have been saved +
+
+ ) + ; + }, + error: 'Error', }); posthog?.capture("Connection_strategy_API_KEY_created", { id_project: idProject, @@ -168,22 +253,61 @@ export function ConnectorDisplay({ item }: ItemDisplayProps) { } if (performUpdate) { const dataToUpdate = mappingConnectionStrategies[0]; - updateCS({ - id_cs: dataToUpdate.id_connection_strategy, - updateToggle: false, - status: dataToUpdate.status, - attributes: ["username", "secret"], - values: [username, secret] - }) + toast.promise( + updateCsPromise({ + id_cs: dataToUpdate.id_connection_strategy, + updateToggle: false, + status: dataToUpdate.status, + attributes: ["username", "secret"], + values: [username, secret] + }), + { + loading: 'Loading...', + success: (data: any) => { + queryClient.setQueryData(['connection-strategies'], (oldQueryData = []) => { + return oldQueryData.map((CS) => CS.id_connection_strategy === data.id_connection_strategy ? data : CS) + }); + return ( +
+ +
+ Changes have been saved +
+
+ ) + ; + }, + error: 'Error', + }); posthog?.capture("Connection_strategy_BASIC_AUTH_updated", { id_project: idProject, mode: config.DISTRIBUTION }); + } else { - createCS({ - type: providerToType(item?.name, item?.vertical!, AuthStrategy.basic), - attributes: ["username", "secret"], - values: [username, secret] + toast.promise( + createCsPromise({ + type: providerToType(item?.name, item?.vertical!, AuthStrategy.basic), + attributes: ["username", "secret"], + values: [username, secret] + }), + { + loading: 'Loading...', + success: (data: any) => { + queryClient.setQueryData(['connections-strategies'], (oldQueryData = []) => { + return [...oldQueryData, data]; + }); + return ( +
+ +
+ Changes have been saved +
+
+ ) + ; + }, + error: 'Error', }); posthog?.capture("Connection_strategy_BASIC_AUTH_created", { id_project: idProject, @@ -228,10 +352,31 @@ export function ConnectorDisplay({ item }: ItemDisplayProps) { const handleSwitchChange = (enabled: boolean) => { if (mappingConnectionStrategies && mappingConnectionStrategies.length > 0) { const dataToUpdate = mappingConnectionStrategies[0]; - updateCS({ - id_cs: dataToUpdate.id_connection_strategy, - updateToggle: true + toast.promise( + updateCsPromise({ + id_cs: dataToUpdate.id_connection_strategy, + updateToggle: true + }), + { + loading: 'Loading...', + success: (data: any) => { + queryClient.setQueryData(['connection-strategies'], (oldQueryData = []) => { + return oldQueryData.map((CS) => CS.id_connection_strategy === data.id_connection_strategy ? data : CS) + }); + return ( +
+ +
+ Changes have been saved +
+
+ ) + ; + }, + error: 'Error', }); + + setSwitchEnabled(enabled); } }; diff --git a/apps/client-ts/src/components/Configuration/FieldMappings/FieldMappingModal.tsx b/apps/client-ts/src/components/Configuration/FieldMappings/FieldMappingModal.tsx index 661a6aa3e..62d2a0c8c 100644 --- a/apps/client-ts/src/components/Configuration/FieldMappings/FieldMappingModal.tsx +++ b/apps/client-ts/src/components/Configuration/FieldMappings/FieldMappingModal.tsx @@ -38,7 +38,7 @@ import useMapField from "@/hooks/create/useMapField" import { useEffect, useState } from "react" import useFieldMappings from "@/hooks/get/useFieldMappings" import useProviderProperties from "@/hooks/get/useProviderProperties" -import { standardObjects } from "@panora/shared/src/standardObjects" +import { standardObjects } from "@panora/shared" import useProjectStore from "@/state/projectStore" import useLinkedUsers from "@/hooks/get/useLinkedUsers" import { zodResolver } from "@hookform/resolvers/zod" @@ -48,6 +48,9 @@ import { usePostHog } from 'posthog-js/react' import config from "@/lib/config" import { CRM_PROVIDERS } from "@panora/shared" import useDefineField from "@/hooks/create/useDefineField" +import { toast } from "sonner" +import { Badge } from "@/components/ui/badge" +import { useQueryClient } from "@tanstack/react-query" const defineFormSchema = z.object({ @@ -106,10 +109,11 @@ export function FModal({ onClose }: {onClose: () => void}) { const [ linkedUserId, sourceProvider ] = mapForm.watch(['linkedUserId', 'sourceProvider']); const {idProject} = useProjectStore(); + const queryClient = useQueryClient(); const { data: mappings } = useFieldMappings(); - const { mutate: mutateDefineField } = useDefineField(); - const { mutate: mutateMapField } = useMapField(); + const { defineMappingPromise } = useDefineField(); + const { mapMappingPromise } = useMapField(); const { data: linkedUsers } = useLinkedUsers(); // TODO: HANDLE VERTICAL AND PROVIDERS FOR CUSTOM MAPPINGS const { data: sourceCustomFields, error, isLoading } = useProviderProperties(linkedUserId,sourceProvider, "crm"); @@ -125,12 +129,32 @@ export function FModal({ onClose }: {onClose: () => void}) { function onDefineSubmit(values: z.infer) { - console.log(values) - mutateDefineField({ - object_type_owner: values.standardModel, - name: values.fieldName, - description: values.fieldDescription, - data_type: values.fieldType, + toast.promise( + defineMappingPromise({ + object_type_owner: values.standardModel, + name: values.fieldName, + description: values.fieldDescription, + data_type: values.fieldType, + }), + { + loading: 'Loading...', + success: (data: any) => { + queryClient.setQueryData(['mappings'], (oldQueryData = []) => { + return [...oldQueryData, data]; + }); + return ( +
+ +
+ Custom field + {`${ values.fieldName }`} + has been defined +
+
+ ) + ; + }, + error: 'Error', }); posthog?.capture("field_defined", { id_project: idProject, @@ -140,12 +164,32 @@ export function FModal({ onClose }: {onClose: () => void}) { } function onMapSubmit(values: z.infer) { - console.log(values) - mutateMapField({ - attributeId: values.attributeId.trim(), - source_custom_field_id: values.sourceCustomFieldId, - source_provider: values.sourceProvider, - linked_user_id: values.linkedUserId, + toast.promise( + mapMappingPromise({ + attributeId: values.attributeId.trim(), + source_custom_field_id: values.sourceCustomFieldId, + source_provider: values.sourceProvider, + linked_user_id: values.linkedUserId, + }), + { + loading: 'Loading...', + success: (data: any) => { + queryClient.setQueryData(['mappings'], (oldQueryData = []) => { + return [...oldQueryData, data]; + }); + return ( +
+ +
+ Custom field + {`${data.name}`} + has been mapped +
+
+ ) + ; + }, + error: 'Error', }); posthog?.capture("field_mapped", { id_project: idProject, diff --git a/apps/client-ts/src/components/Configuration/FieldMappings/defineForm.tsx b/apps/client-ts/src/components/Configuration/FieldMappings/defineForm.tsx index 3a3f344e9..89e0c2d66 100644 --- a/apps/client-ts/src/components/Configuration/FieldMappings/defineForm.tsx +++ b/apps/client-ts/src/components/Configuration/FieldMappings/defineForm.tsx @@ -3,7 +3,6 @@ import { Button } from "@/components/ui/button" import { - Card, CardContent, CardDescription, CardFooter, @@ -11,16 +10,9 @@ import { CardTitle, } from "@/components/ui/card" import { Input } from "@/components/ui/input" -import { - Tabs, - TabsContent, - TabsList, - TabsTrigger, -} from "@/components/ui/tabs" import { Form, FormControl, - FormDescription, FormField, FormItem, FormLabel, @@ -34,20 +26,18 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select" -import useMapField from "@/hooks/create/useMapField" -import { useEffect, useState } from "react" import useFieldMappings from "@/hooks/get/useFieldMappings" -import useProviderProperties from "@/hooks/get/useProviderProperties" -import { standardObjects } from "@panora/shared/src/standardObjects" +import { standardObjects } from "@panora/shared"; import useProjectStore from "@/state/projectStore" -import useLinkedUsers from "@/hooks/get/useLinkedUsers" import { zodResolver } from "@hookform/resolvers/zod" import { useForm } from "react-hook-form" import * as z from "zod" import { usePostHog } from 'posthog-js/react' import config from "@/lib/config" -import { CRM_PROVIDERS } from "@panora/shared" import useDefineField from "@/hooks/create/useDefineField" +import { toast } from "sonner" +import { Badge } from "@/components/ui/badge" +import { useQueryClient } from "@tanstack/react-query" const defineFormSchema = z.object({ @@ -64,7 +54,7 @@ const defineFormSchema = z.object({ message: "fieldType must be at least 2 characters.", }), }) - + export function DefineForm({ onClose }: {onClose: () => void}) { @@ -81,19 +71,39 @@ export function DefineForm({ onClose }: {onClose: () => void}) { const {idProject} = useProjectStore(); const { data: mappings } = useFieldMappings(); - const { mutate: mutateDefineField } = useDefineField(); + const { defineMappingPromise } = useDefineField(); - - const posthog = usePostHog() + const queryClient = useQueryClient(); + const posthog = usePostHog() function onDefineSubmit(values: z.infer) { - console.log(values) - mutateDefineField({ - object_type_owner: values.standardModel, - name: values.fieldName, - description: values.fieldDescription, - data_type: values.fieldType, + toast.promise( + defineMappingPromise({ + object_type_owner: values.standardModel, + name: values.fieldName, + description: values.fieldDescription, + data_type: values.fieldType, + }), + { + loading: 'Loading...', + success: (data: any) => { + queryClient.setQueryData(['mappings'], (oldQueryData = []) => { + return [...oldQueryData, data]; + }); + return ( +
+ +
+ Custom field + {`${ values.fieldName }`} + has been defined +
+
+ ) + ; + }, + error: 'Error', }); posthog?.capture("field_defined", { id_project: idProject, diff --git a/apps/client-ts/src/components/Configuration/FieldMappings/mapForm.tsx b/apps/client-ts/src/components/Configuration/FieldMappings/mapForm.tsx index 70e884773..436ca01af 100644 --- a/apps/client-ts/src/components/Configuration/FieldMappings/mapForm.tsx +++ b/apps/client-ts/src/components/Configuration/FieldMappings/mapForm.tsx @@ -37,8 +37,12 @@ import { useForm } from "react-hook-form" import * as z from "zod" import { usePostHog } from 'posthog-js/react' import config from "@/lib/config" -import { CRM_PROVIDERS, providersArray } from "@panora/shared" +import { providersArray } from "@panora/shared" import { LoadingSpinner } from "@/components/ui/loading-spinner" +import { toast } from "sonner" +import { Badge } from "@/components/ui/badge" +import { useQueryClient } from "@tanstack/react-query" + const mapFormSchema = z.object({ attributeId: z.string().min(2, { @@ -71,9 +75,10 @@ export function MapForm({ onClose, fieldToMap }: {onClose: () => void; fieldToMa const [connectorVertical, setConnectorVertical] = useState(""); const {idProject} = useProjectStore(); + const queryClient = useQueryClient(); const { data: mappings } = useFieldMappings(); - const { mutate: mutateMapField } = useMapField(); + const { mapMappingPromise } = useMapField(); const { data: linkedUsers } = useLinkedUsers(); const { data: sourceCustomFields, error, isLoading } = useProviderProperties(linkedUserId, sourceProvider, connectorVertical); const connectors = providersArray(); @@ -91,11 +96,32 @@ export function MapForm({ onClose, fieldToMap }: {onClose: () => void; fieldToMa } function onMapSubmit(values: z.infer) { - mutateMapField({ - attributeId: values.attributeId.trim(), - source_custom_field_id: values.sourceCustomFieldId, - source_provider: values.sourceProvider, - linked_user_id: values.linkedUserId, + toast.promise( + mapMappingPromise({ + attributeId: values.attributeId.trim(), + source_custom_field_id: values.sourceCustomFieldId, + source_provider: values.sourceProvider, + linked_user_id: values.linkedUserId, + }), + { + loading: 'Loading...', + success: (data: any) => { + queryClient.setQueryData(['mappings'], (oldQueryData = []) => { + return [...oldQueryData, data]; + }); + return ( +
+ +
+ Custom field + {`${data.name}`} + has been mapped +
+
+ ) + ; + }, + error: 'Error', }); posthog?.capture("field_mapped", { id_project: idProject, diff --git a/apps/client-ts/src/components/Configuration/LinkedUsers/AddLinkedAccount.tsx b/apps/client-ts/src/components/Configuration/LinkedUsers/AddLinkedAccount.tsx index a5c388f82..8780b6156 100644 --- a/apps/client-ts/src/components/Configuration/LinkedUsers/AddLinkedAccount.tsx +++ b/apps/client-ts/src/components/Configuration/LinkedUsers/AddLinkedAccount.tsx @@ -26,24 +26,26 @@ import { FormMessage, } from "@/components/ui/form" import { Input } from "@/components/ui/input" -import { Label } from "@/components/ui/label" import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover" import { PlusCircledIcon } from "@radix-ui/react-icons" -import { cn } from "@/lib/utils" import { useState } from "react" import useCreateLinkedUser from "@/hooks/create/useCreateLinkedUser" -import useOrganisationStore from "@/state/organisationStore" import useProjectStore from "@/state/projectStore" import { zodResolver } from "@hookform/resolvers/zod" import { useForm } from "react-hook-form" import * as z from "zod" import { usePostHog } from 'posthog-js/react' import config from "@/lib/config" -import { PlusCircle } from "lucide-react" +import { FileUploader } from "@/components/ui/file-uploader" +import * as XLSX from 'xlsx'; +import { toast } from "sonner" +import { Badge } from "@/components/ui/badge" +import { useQueryClient } from "@tanstack/react-query" +import useCreateBatchLinkedUser from "@/hooks/create/useCreateBatchLinkedUser" interface LinkedUserModalObj { open: boolean; @@ -61,8 +63,14 @@ const AddLinkedAccount = () => { open: false, import: false }) + const [files, setFiles] = useState([]) + const [importing, setImporting] = useState(false); + const [successImporting, setSuccessImporting] = useState(false); - const { mutate } = useCreateLinkedUser(); + const { createLinkedUserPromise } = useCreateLinkedUser(); + const { createBatchLinkedUserPromise } = useCreateBatchLinkedUser(); + + const queryClient = useQueryClient(); const handleOpenChange = (open: boolean) => { setShowNewLinkedUserDialog(prevState => ({ ...prevState, open })); @@ -81,11 +89,31 @@ const AddLinkedAccount = () => { }) function onSubmit(values: z.infer) { - console.log(values) - mutate({ - linked_user_origin_id: values.linkedUserIdentifier, - alias: "", //TODO - id_project: idProject + toast.promise( + createLinkedUserPromise({ + linked_user_origin_id: values.linkedUserIdentifier, + alias: "", //TODO + id_project: idProject + }), + { + loading: 'Loading...', + success: (data: any) => { + queryClient.setQueryData(['linked-users'], (oldQueryData = []) => { + return [...oldQueryData, data]; + }); + return ( +
+ +
+ Linked account + {`${data.linked_user_origin_id}`} + has been created +
+
+ ) + ; + }, + error: 'Error', }); setShowNewLinkedUserDialog({open: false}) posthog?.capture("linked_account_created", { @@ -95,6 +123,61 @@ const AddLinkedAccount = () => { form.reset() } + const handleImport = async () => { + if (files.length === 0) return; + const file = files[0]; + const reader = new FileReader(); + reader.onload = async (e) => { + const data = e.target?.result; + const workbook = XLSX.read(data, { type: 'binary' }); + const sheetName = workbook.SheetNames[0]; + const worksheet = workbook.Sheets[sheetName]; + const json = XLSX.utils.sheet_to_json(worksheet); + if (json.length > 0) { + setImporting(true); + const ids: string[] = []; // Initialize an empty array to hold the IDs + json.forEach((row: any) => { + const linked_user_origin_id = row[Object.keys(row)[0]]; + if(linked_user_origin_id){ + ids.push(linked_user_origin_id); + } + }); + toast.promise( + createBatchLinkedUserPromise({ + linked_user_origin_ids: ids, + alias: "", //TODO + id_project: idProject + }), + { + loading: 'Loading...', + success: (data: any) => { + queryClient.setQueryData(['linked-users'], (oldQueryData = []) => { + return [...oldQueryData, data]; + }); + setSuccessImporting(true); + setImporting(false); + return ( +
+ +
+ Linked accounts have been imported +
+
+ ) + ; + }, + error: 'Error', + }); + posthog?.capture("batch_linked_account_created", { + id_project: idProject, + mode: config.DISTRIBUTION + }) + } + }; + reader.readAsBinaryString(file); + }; + + return ( @@ -162,52 +245,65 @@ const AddLinkedAccount = () => { {showNewLinkedUserDialog.import ? "You can upload a sheet of your existing linked users" : "Add a new linked user to your project"} -
- -
-
- {!showNewLinkedUserDialog.import ? - ( <> -
- ( - - Origin User Identifier - - - - - This is the id of the user in your system. - - - - )} - /> -
- - ) : - <> -
- - -
- + { !showNewLinkedUserDialog.import ? + + +
+
+ +
+ ( + + Origin User Identifier + + + + + This is the id of the user in your system. + + + + )} + /> +
+ + +
+
+ + + + + + + : +
+ {importing ? "Loading ....." : successImporting ? "Success !!" : + <> + + + + + + } -
-
- - - - - - +
+ } +
) diff --git a/apps/client-ts/src/components/Configuration/Webhooks/AddWebhook.tsx b/apps/client-ts/src/components/Configuration/Webhooks/AddWebhook.tsx index 4b4cbc9ff..87b750a53 100644 --- a/apps/client-ts/src/components/Configuration/Webhooks/AddWebhook.tsx +++ b/apps/client-ts/src/components/Configuration/Webhooks/AddWebhook.tsx @@ -26,7 +26,6 @@ import { FormLabel, FormMessage, } from "@/components/ui/form" -import { cn } from "@/lib/utils" import { zodResolver } from "@hookform/resolvers/zod" import { useForm } from "react-hook-form" import * as z from "zod" @@ -34,6 +33,9 @@ import { usePostHog } from 'posthog-js/react' import config from "@/lib/config" import { DataTableFacetedFilterWebhook } from "../../shared/data-table-webhook-scopes" import useCreateWebhook from "@/hooks/create/useCreateWebhook" +import { toast } from "sonner" +import { Badge } from "@/components/ui/badge" +import { useQueryClient } from "@tanstack/react-query" const formSchema = z.object({ @@ -52,8 +54,9 @@ const AddWebhook = () => { const posthog = usePostHog() const {idProject} = useProjectStore(); + const queryClient = useQueryClient(); - const { mutate } = useCreateWebhook(); + const { createWebhookPromise } = useCreateWebhook(); const form = useForm>({ resolver: zodResolver(formSchema), @@ -73,11 +76,32 @@ const AddWebhook = () => { function onSubmit(values: z.infer) { const selectedScopes = values.scopes ? values.scopes.split(' ') : []; console.log({ ...values, scopes: selectedScopes }); - mutate({ - url: values.url, - description: values.description, - id_project: idProject, - scope: selectedScopes, + toast.promise( + createWebhookPromise({ + url: values.url, + description: values.description, + id_project: idProject, + scope: selectedScopes, + }), + { + loading: 'Loading...', + success: (data: any) => { + queryClient.setQueryData(['webhooks'], (oldQueryData = []) => { + return [...oldQueryData, data]; + }); + return ( +
+ +
+ Webhook + {`${data.url}`} + has been created +
+
+ ) + ; + }, + error: 'Error', }); handleOpenChange(false); posthog?.capture("webhook_created", { diff --git a/apps/client-ts/src/components/Configuration/Webhooks/columns.tsx b/apps/client-ts/src/components/Configuration/Webhooks/columns.tsx index 3f12bae8a..7be05a6b2 100644 --- a/apps/client-ts/src/components/Configuration/Webhooks/columns.tsx +++ b/apps/client-ts/src/components/Configuration/Webhooks/columns.tsx @@ -12,25 +12,39 @@ import { DataTableRowActions } from "@/components/shared/data-table-row-actions" import { Switch } from "@/components/ui/switch" import useUpdateWebhookStatus from "@/hooks/update/useUpdateWebhookStatus" import { Webhook } from "./WebhooksPage" - +import { useQueryClient } from "@tanstack/react-query" +import { toast } from "sonner" export function useColumns(webhooks: Webhook[] | undefined, setWebhooks: React.Dispatch>) { const [copiedState, setCopiedState] = useState<{ [key: string]: boolean }>({}); - const { mutate } = useUpdateWebhookStatus(); + const { updateWebhookPromise } = useUpdateWebhookStatus(); const disableWebhook = (webhook_id: string, status: boolean) => { - mutate({ - id: webhook_id, - active: status, - }, { - onSuccess: () => { - const index = webhooks!.findIndex(webhook => webhook.id_webhook_endpoint === webhook_id); - if (index !== -1) { - const updatedWebhooks = [...webhooks!]; - updatedWebhooks[index].active = status; - setWebhooks(updatedWebhooks); - } - } + toast.promise( + updateWebhookPromise({ + id: webhook_id, + active: status, + }), + { + loading: 'Loading...', + success: (data: any) => { + const index = webhooks!.findIndex(webhook => webhook.id_webhook_endpoint === webhook_id); + if (index !== -1) { + const updatedWebhooks = [...webhooks!]; + updatedWebhooks[index].active = status; + setWebhooks(updatedWebhooks); + } + return ( +
+ +
+ Changes have been saved +
+
+ ) + ; + }, + error: 'Error', }); } @@ -40,6 +54,12 @@ export function useColumns(webhooks: Webhook[] | undefined, setWebhooks: React.D ...prevState, [token]: true, })); + toast.success("Webhook copied", { + action: { + label: "Close", + onClick: () => console.log("Close"), + }, + }) setTimeout(() => { setCopiedState((prevState) => ({ ...prevState, diff --git a/apps/client-ts/src/components/Connection/AddConnectionButton.tsx b/apps/client-ts/src/components/Connection/AddConnectionButton.tsx index 835d876f5..c226f451b 100644 --- a/apps/client-ts/src/components/Connection/AddConnectionButton.tsx +++ b/apps/client-ts/src/components/Connection/AddConnectionButton.tsx @@ -43,6 +43,9 @@ import { useForm } from "react-hook-form" import * as z from "zod" import { usePostHog } from 'posthog-js/react' import config from "@/lib/config" +import { toast } from "sonner" +import { useQueryClient } from "@tanstack/react-query" +import useMagicLinkStore from "@/state/magicLinkStore" const formSchema = z.object({ linkedUserIdentifier: z.string().min(2, { @@ -72,10 +75,11 @@ const AddConnectionButton = ({ const posthog = usePostHog() - const { mutate, isError, error } = useCreateMagicLink(); - + const { createMagicLinkPromise } = useCreateMagicLink(); + const {setUniqueLink} = useMagicLinkStore(); const {nameOrg} = useOrganisationStore(); const {idProject} = useProjectStore(); + const queryClient = useQueryClient(); const form = useForm>({ resolver: zodResolver(formSchema), @@ -86,18 +90,33 @@ const AddConnectionButton = ({ }) function onSubmit(values: z.infer) { - console.log(values) - mutate({ - linked_user_origin_id: values.linkedUserIdentifier, - email: values.linkedUserMail, - alias: nameOrg, - id_project: idProject + toast.promise( + createMagicLinkPromise({ + linked_user_origin_id: values.linkedUserIdentifier, + email: values.linkedUserMail, + alias: nameOrg, + id_project: idProject + }), + { + loading: 'Loading...', + success: (data: any) => { + setUniqueLink(data.id_invite_link); + queryClient.setQueryData(['magic-links'], (oldQueryData = []) => { + return [...oldQueryData, data]; + }); + return ( +
+ +
+ Magic Link has been created +
+
+ ) + ; + }, + error: 'Error', }); - if(isError) { - console.log(error); - } - posthog?.capture("magic_link_created", { id_project: idProject, mode: config.DISTRIBUTION @@ -251,11 +270,11 @@ const AddConnectionButton = ({
- - + diff --git a/apps/client-ts/src/components/Connection/ConnectionTable.tsx b/apps/client-ts/src/components/Connection/ConnectionTable.tsx index 220130552..13f0aa700 100644 --- a/apps/client-ts/src/components/Connection/ConnectionTable.tsx +++ b/apps/client-ts/src/components/Connection/ConnectionTable.tsx @@ -115,7 +115,7 @@ export default function ConnectionTable() {
- diff --git a/apps/client-ts/src/components/Connection/CopyLinkInput.tsx b/apps/client-ts/src/components/Connection/CopyLinkInput.tsx index 3bb7f773b..538861246 100644 --- a/apps/client-ts/src/components/Connection/CopyLinkInput.tsx +++ b/apps/client-ts/src/components/Connection/CopyLinkInput.tsx @@ -6,6 +6,7 @@ import useMagicLinkStore from '@/state/magicLinkStore'; import config from '@/lib/config'; import { useState } from 'react'; import { LoadingSpinner } from './LoadingSpinner'; +import { toast } from 'sonner'; const CopyLinkInput = () => { const [copied, setCopied] = useState(false); @@ -15,6 +16,12 @@ const CopyLinkInput = () => { const handleCopy = async () => { try { await navigator.clipboard.writeText(`${config.MAGIC_LINK_DOMAIN}/?uniqueLink=${uniqueLink}`); + toast.success("Magic link copied", { + action: { + label: "Close", + onClick: () => console.log("Close"), + }, + }) setCopied(true); setTimeout(() => setCopied(false), 2000); // Reset copied state after 2 seconds } catch (err) { diff --git a/apps/client-ts/src/components/Connection/columns.tsx b/apps/client-ts/src/components/Connection/columns.tsx index b331c636d..f8d951e4d 100644 --- a/apps/client-ts/src/components/Connection/columns.tsx +++ b/apps/client-ts/src/components/Connection/columns.tsx @@ -14,7 +14,12 @@ const connectionTokenComponent = ({row}:{row:any}) => { const handleCopy = async () => { try { await navigator.clipboard.writeText(row.getValue("connectionToken")); - toast("Connection Token copied to clipboard!!") + toast.success("Connection token copied", { + action: { + label: "Close", + onClick: () => console.log("Close"), + }, + }) } catch (err) { console.error('Failed to copy: ', err); } @@ -24,7 +29,6 @@ const connectionTokenComponent = ({row}:{row:any}) => {
{truncateMiddle(row.getValue("connectionToken"),6)} -
) diff --git a/apps/client-ts/src/components/shared/data-table-row-actions.tsx b/apps/client-ts/src/components/shared/data-table-row-actions.tsx index 07f035a17..d81354487 100644 --- a/apps/client-ts/src/components/shared/data-table-row-actions.tsx +++ b/apps/client-ts/src/components/shared/data-table-row-actions.tsx @@ -13,6 +13,9 @@ import { } from "@/components/ui/dropdown-menu" import useDeleteApiKey from "@/hooks/delete/useDeleteApiKey" import useDeleteWebhook from "@/hooks/delete/useDeleteWebhook" +import { toast } from "sonner" +import { Badge } from "@/components/ui/badge" +import { useQueryClient } from "@tanstack/react-query" interface DataTableRowActionsProps { row: Row; @@ -24,20 +27,63 @@ export function DataTableRowActions({ object }: DataTableRowActionsProps) { - const {mutate: removeApiKey} = useDeleteApiKey(); - const {mutate: removeWebhook} = useDeleteWebhook(); + const {deleteApiKeyPromise} = useDeleteApiKey(); + const {deleteWebhookPromise} = useDeleteWebhook(); + const queryClient = useQueryClient(); const handleDeletion = () => { switch(object) { case 'webhook': - removeWebhook({ - id_webhook: (row.original as any).id_webhook_endpoint - }) + toast.promise( + deleteWebhookPromise({ + id_webhook: (row.original as any).id_webhook_endpoint + }), + { + loading: 'Loading...', + success: (data: any) => { + queryClient.setQueryData(['webhooks'], (oldQueryData = []) => { + return oldQueryData.filter((wh) => wh.id_webhook_endpoint !== data.id_webhook_endpoint); + }); + return ( +
+ +
+ Webhook + {`${data.url}`} + has been deleted +
+
+ ) + ; + }, + error: 'Error', + }); break; case 'api-key': - removeApiKey({ - id_api_key: (row.original as any).id_api_key - }) + toast.promise( + deleteApiKeyPromise({ + id_api_key: (row.original as any).id_api_key + }), + { + loading: 'Loading...', + success: (data: any) => { + queryClient.setQueryData(['api-keys'], (oldQueryData = []) => { + return oldQueryData.filter((api_key) => api_key.id_api_key !== data.id_api_key); + }); + return ( +
+ +
+ Api key + {`${data.name}`} + has been deleted +
+
+ ) + ; + }, + error: 'Error', + }); break; default: break; diff --git a/apps/client-ts/src/components/shared/team-switcher.tsx b/apps/client-ts/src/components/shared/team-switcher.tsx index 01533a572..e1b3826e1 100644 --- a/apps/client-ts/src/components/shared/team-switcher.tsx +++ b/apps/client-ts/src/components/shared/team-switcher.tsx @@ -57,7 +57,9 @@ import { Skeleton } from "@/components/ui/skeleton"; import useProfileStore from "@/state/profileStore" import { projects as Project } from 'api'; import useRefreshAccessTokenMutation from "@/hooks/create/useRefreshAccessToken" - +import { toast } from "sonner" +import { useQueryClient } from "@tanstack/react-query" +import { Badge } from "../ui/badge" const projectFormSchema = z.object({ projectName: z.string().min(2, { @@ -92,7 +94,9 @@ export default function TeamSwitcher({ className ,projects}: TeamSwitcherProps) setShowNewDialog(prevState => ({ ...prevState, open })); }; - const { mutate: mutateProject } = useProjectMutation(); + const queryClient = useQueryClient(); + + const { createProjectsPromise } = useProjectMutation(); const projectForm = useForm>({ resolver: zodResolver(projectFormSchema), @@ -102,10 +106,30 @@ export default function TeamSwitcher({ className ,projects}: TeamSwitcherProps) }) function onProjectSubmit(values: z.infer) { - console.log(values) - mutateProject({ - name: values.projectName, - id_user: profile!.id_user, + toast.promise( + createProjectsPromise({ + name: values.projectName, + id_user: profile!.id_user, + }), + { + loading: 'Loading...', + success: (data: any) => { + queryClient.setQueryData(['projects'], (oldQueryData = []) => { + return [...oldQueryData, data]; + }); + return ( +
+ +
+ Project + {`${data.name}`} + has been created +
+
+ ) + ; + }, + error: 'Error', }); setShowNewDialog({open: false}) projectForm.reset(); diff --git a/apps/client-ts/src/components/ui/button2.tsx b/apps/client-ts/src/components/ui/button2.tsx new file mode 100644 index 000000000..b1f45e2fc --- /dev/null +++ b/apps/client-ts/src/components/ui/button2.tsx @@ -0,0 +1,108 @@ +import * as React from "react"; +import { Slot, Slottable } from "@radix-ui/react-slot"; +import { cva, type VariantProps } from "class-variance-authority"; +import { cn } from "@/lib/utils"; + +const buttonVariants = cva( + "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground hover:bg-primary/90", + destructive: + "bg-destructive text-destructive-foreground hover:bg-destructive/90", + outline: + "border border-input bg-background hover:bg-accent hover:text-accent-foreground", + secondary: + "bg-secondary text-secondary-foreground hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", + expandIcon: + "group relative text-primary-foreground bg-primary hover:bg-primary/90", + ringHover: + "bg-primary text-primary-foreground transition-all duration-300 hover:bg-primary/90 hover:ring-2 hover:ring-primary/90 hover:ring-offset-2", + shine: + "text-primary-foreground animate-shine bg-gradient-to-r from-primary via-primary/75 to-primary bg-[length:400%_100%] ", + gooeyRight: + "text-primary-foreground relative bg-primary z-0 overflow-hidden transition-all duration-500 before:absolute before:inset-0 before:-z-10 before:translate-x-[150%] before:translate-y-[150%] before:scale-[2.5] before:rounded-[100%] before:bg-gradient-to-r from-zinc-400 before:transition-transform before:duration-1000 hover:before:translate-x-[0%] hover:before:translate-y-[0%] ", + gooeyLeft: + "text-primary-foreground relative bg-primary z-0 overflow-hidden transition-all duration-500 after:absolute after:inset-0 after:-z-10 after:translate-x-[-150%] after:translate-y-[150%] after:scale-[2.5] after:rounded-[100%] after:bg-gradient-to-l from-zinc-400 after:transition-transform after:duration-1000 hover:after:translate-x-[0%] hover:after:translate-y-[0%] ", + linkHover1: + "relative after:absolute after:bg-primary after:bottom-2 after:h-[1px] after:w-2/3 after:origin-bottom-left after:scale-x-100 hover:after:origin-bottom-right hover:after:scale-x-0 after:transition-transform after:ease-in-out after:duration-300", + linkHover2: + "relative after:absolute after:bg-primary after:bottom-2 after:h-[1px] after:w-2/3 after:origin-bottom-right after:scale-x-0 hover:after:origin-bottom-left hover:after:scale-x-100 after:transition-transform after:ease-in-out after:duration-300", + }, + size: { + default: "h-10 px-4 py-2", + sm: "h-9 rounded-md px-3", + lg: "h-11 rounded-md px-8", + icon: "h-10 w-10", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +); + +interface IconProps { + Icon: React.ElementType; + iconPlacement: "left" | "right"; +} + +interface IconRefProps { + Icon?: never; + iconPlacement?: undefined; +} + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean; +} + +export type ButtonIconProps = IconProps | IconRefProps; + +const Button = React.forwardRef< + HTMLButtonElement, + ButtonProps & ButtonIconProps +>( + ( + { + className, + variant, + size, + asChild = false, + Icon, + iconPlacement, + ...props + }, + ref + ) => { + const Comp = asChild ? Slot : "button"; + return ( + + {Icon && iconPlacement === "left" && ( +
+ +
+ )} + {props.children} + {Icon && iconPlacement === "right" && ( +
+ +
+ )} +
+ ); + } +); +Button.displayName = "Button"; + +export { Button, buttonVariants }; + \ No newline at end of file diff --git a/apps/client-ts/src/components/ui/file-uploader.tsx b/apps/client-ts/src/components/ui/file-uploader.tsx new file mode 100644 index 000000000..dff3cc3b4 --- /dev/null +++ b/apps/client-ts/src/components/ui/file-uploader.tsx @@ -0,0 +1,313 @@ +"use client" + +import * as React from "react" +import { Cross2Icon, UploadIcon } from "@radix-ui/react-icons" +import Dropzone, { + type DropzoneProps, + type FileRejection, +} from "react-dropzone" +import { toast } from "sonner" +import { cn, formatBytes } from "@/lib/utils" +import { useControllableState } from "@/hooks/use-controllable-state" +import { Button } from "@/components/ui/button" +import { Progress } from "@/components/ui/progress" +import { ScrollArea } from "@/components/ui/scroll-area" + +interface FileUploaderProps extends React.HTMLAttributes { + /** + * Value of the uploader. + * @type File[] + * @default undefined + * @example value={files} + */ + value?: File[] + + /** + * Function to be called when the value changes. + * @type React.Dispatch> + * @default undefined + * @example onValueChange={(files) => setFiles(files)} + */ + onValueChange?: React.Dispatch> + + /** + * Function to be called when files are uploaded. + * @type (files: File[]) => Promise + * @default undefined + * @example onUpload={(files) => uploadFiles(files)} + */ + onUpload?: (files: File[]) => Promise + + /** + * Progress of the uploaded files. + * @type Record | undefined + * @default undefined + * @example progresses={{ "file1.png": 50 }} + */ + progresses?: Record + + /** + * Accepted file types for the uploader. + * @type { [key: string]: string[]} + * @default + * ```ts + * { "image/*": [] } + * ``` + * @example accept={["image/png", "image/jpeg"]} + */ + accept?: DropzoneProps["accept"] + + /** + * Maximum file size for the uploader. + * @type number | undefined + * @default 1024 * 1024 * 2 // 2MB + * @example maxSize={1024 * 1024 * 2} // 2MB + */ + maxSize?: DropzoneProps["maxSize"] + + /** + * Maximum number of files for the uploader. + * @type number | undefined + * @default 1 + * @example maxFiles={5} + */ + maxFiles?: DropzoneProps["maxFiles"] + + /** + * Whether the uploader should accept multiple files. + * @type boolean + * @default false + * @example multiple + */ + multiple?: boolean + + /** + * Whether the uploader is disabled. + * @type boolean + * @default false + * @example disabled + */ + disabled?: boolean +} + +export function FileUploader(props: FileUploaderProps) { + const { + value: valueProp, + onValueChange, + onUpload, + progresses, + accept = { "image/*": [] }, + maxSize = 1024 * 1024 * 2, + maxFiles = 1, + multiple = false, + disabled = false, + className, + ...dropzoneProps + } = props + + const [files, setFiles] = useControllableState({ + prop: valueProp, + onChange: onValueChange, + }) + + const onDrop = React.useCallback( + (acceptedFiles: File[], rejectedFiles: FileRejection[]) => { + if (!multiple && maxFiles === 1 && acceptedFiles.length > 1) { + toast.error("Cannot upload more than 1 file at a time") + return + } + + if ((files?.length ?? 0) + acceptedFiles.length > maxFiles) { + toast.error(`Cannot upload more than ${maxFiles} files`) + return + } + + const newFiles = acceptedFiles.map((file) => + Object.assign(file, { + preview: URL.createObjectURL(file), + }) + ) + + const updatedFiles = files ? [...files, ...newFiles] : newFiles + + setFiles(updatedFiles) + + if (rejectedFiles.length > 0) { + rejectedFiles.forEach(({ file }) => { + toast.error(`File ${file.name} was rejected`) + }) + } + + if ( + onUpload && + updatedFiles.length > 0 && + updatedFiles.length <= maxFiles + ) { + const target = + updatedFiles.length > 0 ? `${updatedFiles.length} files` : `file` + + toast.promise(onUpload(updatedFiles), { + loading: `Uploading ${target}...`, + success: () => { + setFiles([]) + return `${target} uploaded` + }, + error: `Failed to upload ${target}`, + }) + } + }, + + [files, maxFiles, multiple, onUpload, setFiles] + ) + + function onRemove(index: number) { + if (!files) return + const newFiles = files.filter((_, i) => i !== index) + setFiles(newFiles) + onValueChange?.(newFiles) + } + + // Revoke preview url when component unmounts + React.useEffect(() => { + return () => { + if (!files) return + files.forEach((file) => { + if (isFileWithPreview(file)) { + URL.revokeObjectURL(file.preview) + } + }) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + const isDisabled = disabled || (files?.length ?? 0) >= maxFiles + + return ( +
+ 1 || multiple} + disabled={isDisabled} + > + {({ getRootProps, getInputProps, isDragActive }) => ( +
+ + {isDragActive ? ( +
+
+
+

+ Drop the files here +

+
+ ) : ( +
+
+
+
+

+ Drag {`'n'`} drop files here, or click to select files +

+

+ You can upload + {maxFiles > 1 + ? ` ${maxFiles === Infinity ? "multiple" : maxFiles} + files (up to ${formatBytes(maxSize)} each)` + : ` a file with ${formatBytes(maxSize)}`} +

+
+
+ )} +
+ )} +
+ {files?.length ? ( + +
+ {files?.map((file, index) => ( + onRemove(index)} + progress={progresses?.[file.name]} + /> + ))} +
+
+ ) : null} +
+ ) +} + +interface FileCardProps { + file: File + onRemove: () => void + progress?: number +} + +function FileCard({ file, progress, onRemove }: FileCardProps) { + return ( +
+
+ {isFileWithPreview(file) ? ( + {file.name} + ) : null} +
+
+

+ {file.name} +

+

+ {formatBytes(file.size)} +

+
+ {progress ? : null} +
+
+
+ +
+
+ ) +} + +function isFileWithPreview(file: File): file is File & { preview: string } { + return "preview" in file && typeof file.preview === "string" +} \ No newline at end of file diff --git a/apps/client-ts/src/components/ui/progress.tsx b/apps/client-ts/src/components/ui/progress.tsx new file mode 100644 index 000000000..f9f34d9ad --- /dev/null +++ b/apps/client-ts/src/components/ui/progress.tsx @@ -0,0 +1,28 @@ +"use client" + +import * as React from "react" +import * as ProgressPrimitive from "@radix-ui/react-progress" + +import { cn } from "@/lib/utils" + +const Progress = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, value, ...props }, ref) => ( + + + +)) +Progress.displayName = ProgressPrimitive.Root.displayName + +export { Progress } \ No newline at end of file diff --git a/apps/client-ts/src/hooks/create/useCreateApiKey.tsx b/apps/client-ts/src/hooks/create/useCreateApiKey.tsx index d31e401f3..469926b6e 100644 --- a/apps/client-ts/src/hooks/create/useCreateApiKey.tsx +++ b/apps/client-ts/src/hooks/create/useCreateApiKey.tsx @@ -1,15 +1,15 @@ import config from '@/lib/config'; -import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { toast } from "sonner" +import { useMutation } from '@tanstack/react-query'; import Cookies from 'js-cookie'; -interface IApiKeyDto { +export interface IApiKeyDto { projectId: string; userId: string; keyName?: string; } + +// Adjusted useCreateApiKey hook to include a promise-returning function const useCreateApiKey = () => { - const queryClient = useQueryClient(); const addApiKey = async (data: IApiKeyDto) => { const response = await fetch(`${config.API_URL}/auth/generate-apikey`, { method: 'POST', @@ -26,41 +26,26 @@ const useCreateApiKey = () => { return response.json(); }; - return useMutation({ - mutationFn: addApiKey, - onMutate: () => { - toast("Api key is being generated !", { - description: "", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) - }, - onError: (error) => { - toast("Api key generation failed !", { - description: error as any, - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) - }, - onSuccess: (data) => { - queryClient.setQueryData(['api-keys'], (oldQueryData = []) => { - return [...oldQueryData, data]; - }); - toast("Api key has been generated !", { - description: "", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) - }, - onSettled: () => { - }, - }); + + // Expose a promise-returning function alongside mutate + const createApiKeyPromise = (data: IApiKeyDto) => { + return new Promise(async (resolve, reject) => { + try { + const result = await addApiKey(data); + resolve(result); + + } catch (error) { + reject(error); + } + }); + }; + + return { + mutate: useMutation({ + mutationFn: addApiKey, + }), + createApiKeyPromise, + }; }; export default useCreateApiKey; diff --git a/apps/client-ts/src/hooks/create/useCreateBatchLinkedUser.tsx b/apps/client-ts/src/hooks/create/useCreateBatchLinkedUser.tsx new file mode 100644 index 000000000..55a812e68 --- /dev/null +++ b/apps/client-ts/src/hooks/create/useCreateBatchLinkedUser.tsx @@ -0,0 +1,44 @@ +import config from '@/lib/config'; +import { useMutation } from '@tanstack/react-query'; + +interface ILinkedUserDto { + linked_user_origin_ids: string[]; + alias: string; + id_project: string; +} +const useCreateBatchLinkedUser = () => { + const add = async (linkedUserData: ILinkedUserDto) => { + const response = await fetch(`${config.API_URL}/linked-users/batch`, { + method: 'POST', + body: JSON.stringify(linkedUserData), + headers: { + 'Content-Type': 'application/json', + }, + }); + + if (!response.ok) { + throw new Error('Failed to batch add linked user'); + } + + return response.json(); + }; + const createBatchLinkedUserPromise = (data: ILinkedUserDto) => { + return new Promise(async (resolve, reject) => { + try { + const result = await add(data); + resolve(result); + + } catch (error) { + reject(error); + } + }); + }; + return { + mutationFn: useMutation({ + mutationFn: add, + }), + createBatchLinkedUserPromise + }; +}; + +export default useCreateBatchLinkedUser; diff --git a/apps/client-ts/src/hooks/create/useCreateConnectionStrategy.tsx b/apps/client-ts/src/hooks/create/useCreateConnectionStrategy.tsx index 6ef2748cc..72f7ab198 100644 --- a/apps/client-ts/src/hooks/create/useCreateConnectionStrategy.tsx +++ b/apps/client-ts/src/hooks/create/useCreateConnectionStrategy.tsx @@ -1,6 +1,5 @@ import config from '@/lib/config'; -import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { toast } from "sonner" +import { useMutation } from '@tanstack/react-query'; import Cookies from 'js-cookie'; interface IConnectionStrategyDto { @@ -9,17 +8,7 @@ interface IConnectionStrategyDto { values: string[], } -interface IFetchConnectionStrategyDto { - id_cs: string, - type: string, - attributes: string[], - values: string[], -} - - -const useCreateConnectionStrategy = () => { - const queryClient = useQueryClient(); - +const useCreateConnectionStrategy = () => { const add = async (connectionStrategyData: IConnectionStrategyDto) => { const response = await fetch(`${config.API_URL}/connections-strategies/create`, { method: 'POST', @@ -30,49 +19,32 @@ const useCreateConnectionStrategy = () => { }, }); - // console.log(response.status) - if (!response.ok) { throw new Error('Failed to add Connection Strategy'); } return response.json(); }; - return useMutation({ - mutationFn: add, - onMutate: () => { - toast("Connection Strategy is being created !", { - description: "", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) - }, - onError: (error) => { - toast("The creation of Connection Strategy has failed !", { - description: error as any, - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) - }, - onSuccess: (data) => { - queryClient.setQueryData(['connection-strategies'], (oldQueryData = []) => { - return [...oldQueryData, data]; - }); - toast("New Connection Strategy has been created !", { - description: "", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) - }, - onSettled: () => { - }, - }); + + // Expose a promise-returning function alongside mutate + const createCsPromise = (data: IConnectionStrategyDto) => { + return new Promise(async (resolve, reject) => { + try { + const result = await add(data); + resolve(result); + + } catch (error) { + reject(error); + } + }); + }; + + return { + mutationFn: useMutation({ + mutationFn: add, + }), + createCsPromise + }; }; export default useCreateConnectionStrategy; diff --git a/apps/client-ts/src/hooks/create/useCreateLinkedUser.tsx b/apps/client-ts/src/hooks/create/useCreateLinkedUser.tsx index bc35795ea..4305ad914 100644 --- a/apps/client-ts/src/hooks/create/useCreateLinkedUser.tsx +++ b/apps/client-ts/src/hooks/create/useCreateLinkedUser.tsx @@ -1,14 +1,11 @@ import config from '@/lib/config'; -import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { toast } from "sonner" +import { useMutation } from '@tanstack/react-query'; interface ILinkedUserDto { linked_user_origin_id: string; alias: string; id_project: string; } -const useCreateLinkedUser = () => { - const queryClient = useQueryClient(); - +const useCreateLinkedUser = () => { const add = async (linkedUserData: ILinkedUserDto) => { const response = await fetch(`${config.API_URL}/linked-users`, { method: 'POST', @@ -24,41 +21,23 @@ const useCreateLinkedUser = () => { return response.json(); }; - return useMutation({ - mutationFn: add, - onMutate: () => { - toast("Linked user is being created !", { - description: "", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) - }, - onError: (error) => { - toast("The creation of linked user has failed !", { - description: error as any, - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) - }, - onSuccess: (data) => { - queryClient.setQueryData(['linked-users'], (oldQueryData = []) => { - return [...oldQueryData, data]; - }); - toast("New linked user has been created !", { - description: "", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) - }, - onSettled: () => { - }, - }); + const createLinkedUserPromise = (data: ILinkedUserDto) => { + return new Promise(async (resolve, reject) => { + try { + const result = await add(data); + resolve(result); + + } catch (error) { + reject(error); + } + }); + }; + return { + mutationFn: useMutation({ + mutationFn: add, + }), + createLinkedUserPromise + }; }; export default useCreateLinkedUser; diff --git a/apps/client-ts/src/hooks/create/useCreateLogin.tsx b/apps/client-ts/src/hooks/create/useCreateLogin.tsx index 64a427e9d..c3d37a488 100644 --- a/apps/client-ts/src/hooks/create/useCreateLogin.tsx +++ b/apps/client-ts/src/hooks/create/useCreateLogin.tsx @@ -1,8 +1,5 @@ import config from '@/lib/config'; import { useMutation } from '@tanstack/react-query'; -import { toast } from "sonner" -import useProfileStore from '@/state/profileStore'; -import Cookies from 'js-cookie'; type IUserDto = { id_user: string; @@ -23,9 +20,6 @@ interface ILoginOutputDto { } const useCreateLogin = () => { - - const {setProfile} = useProfileStore() - const add = async (userData: ILoginInputDto) => { // Fetch the token const response = await fetch(`${config.API_URL}/auth/login`, { @@ -42,40 +36,23 @@ const useCreateLogin = () => { return response.json(); }; - return useMutation({ - mutationFn: add, - onMutate: () => { - // toast("Logging the user !", { - // description: "", - // action: { - // label: "Close", - // onClick: () => console.log("Close"), - // }, - // }) - }, - onError: (error) => { - toast.error("User generation failed !", { - description: error as any, - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) - }, - onSuccess: (data : ILoginOutputDto) => { - setProfile(data.user); - Cookies.set('access_token',data.access_token,{expires:1}); - toast.success("User has been generated !", { - description: "", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) - }, - onSettled: () => { - }, - }); + const loginPromise = (data: ILoginInputDto) => { + return new Promise(async (resolve, reject) => { + try { + const result = await add(data); + resolve(result); + + } catch (error) { + reject(error); + } + }); + }; + return { + mutationFn: useMutation({ + mutationFn: add, + }), + loginPromise + } }; export default useCreateLogin; diff --git a/apps/client-ts/src/hooks/create/useCreateMagicLink.tsx b/apps/client-ts/src/hooks/create/useCreateMagicLink.tsx index abd3a876e..c3115fd81 100644 --- a/apps/client-ts/src/hooks/create/useCreateMagicLink.tsx +++ b/apps/client-ts/src/hooks/create/useCreateMagicLink.tsx @@ -1,7 +1,5 @@ -import useMagicLinkStore from '@/state/magicLinkStore'; import config from '@/lib/config'; import { useMutation } from '@tanstack/react-query'; -import { toast } from "sonner" import Cookies from 'js-cookie'; interface ILinkDto { @@ -28,41 +26,23 @@ const useCreateMagicLink = () => { return response.json(); }; - const {setUniqueLink} = useMagicLinkStore(); - - return useMutation({ - mutationFn: add, - onMutate: () => { - toast("Magic link is being generated !", { - description: "", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) - }, - onError: (error) => { - toast("Magic link generation failed !", { - description: error as any, - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) - }, - onSuccess: (data) => { - setUniqueLink(data.id_invite_link) - toast("Magic link has been generated!", { - description: "", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) - }, - onSettled: () => { - }, - }); + const createMagicLinkPromise = (data: ILinkDto) => { + return new Promise(async (resolve, reject) => { + try { + const result = await add(data); + resolve(result); + + } catch (error) { + reject(error); + } + }); + }; + return { + mutationFn: useMutation({ + mutationFn: add, + }), + createMagicLinkPromise + }; }; export default useCreateMagicLink; diff --git a/apps/client-ts/src/hooks/create/useCreateProfile.tsx b/apps/client-ts/src/hooks/create/useCreateProfile.tsx index d4500d569..cc1f19bc7 100644 --- a/apps/client-ts/src/hooks/create/useCreateProfile.tsx +++ b/apps/client-ts/src/hooks/create/useCreateProfile.tsx @@ -1,6 +1,5 @@ import config from '@/lib/config'; -import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { toast } from "sonner" +import { useMutation } from '@tanstack/react-query'; interface IProfileDto { first_name: string; @@ -11,9 +10,7 @@ interface IProfileDto { id_organization?: string } -const useCreateProfile = () => { - const queryClient = useQueryClient(); - +const useCreateProfile = () => { const add = async (data: IProfileDto) => { const response = await fetch(`${config.API_URL}/auth/users/create`, { method: 'POST', @@ -29,45 +26,32 @@ const useCreateProfile = () => { return response.json(); }; - return useMutation({ - mutationFn: add, - onMutate: () => { - toast("Profile is being created !", { - description: "", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) - }, - onError: (error) => { - toast("Profile creation has failed !", { - description: error as any, - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) - }, - onSuccess: (data) => { - queryClient.invalidateQueries({ + const createProfilePromise = (data: IProfileDto) => { + return new Promise(async (resolve, reject) => { + try { + const result = await add(data); + resolve(result); + + } catch (error) { + reject(error); + } + }); + }; + return { + mutationFn: useMutation({ + mutationFn: add, + }), + createProfilePromise + /* + queryClient.invalidateQueries({ queryKey: ['profiles'], refetchType: 'active', }) queryClient.setQueryData(['profiles'], (oldQueryData = []) => { return [...oldQueryData, data]; }); - toast("Profile has been created !", { - description: "", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) - }, - onSettled: () => { - }, - }); + */ + }; }; export default useCreateProfile; diff --git a/apps/client-ts/src/hooks/create/useCreateProject.tsx b/apps/client-ts/src/hooks/create/useCreateProject.tsx index 94e5fb7fb..98e7e0468 100644 --- a/apps/client-ts/src/hooks/create/useCreateProject.tsx +++ b/apps/client-ts/src/hooks/create/useCreateProject.tsx @@ -1,6 +1,5 @@ import config from '@/lib/config'; -import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { toast } from "sonner" +import { useMutation } from '@tanstack/react-query'; import Cookies from 'js-cookie'; interface IProDto { @@ -8,9 +7,7 @@ interface IProDto { id_user: string; } -const useCreateProject = () => { - const queryClient = useQueryClient(); - +const useCreateProject = () => { const add = async (data: IProDto) => { const response = await fetch(`${config.API_URL}/projects`, { method: 'POST', @@ -27,41 +24,23 @@ const useCreateProject = () => { return response.json(); }; - return useMutation({ - mutationFn: add, - onMutate: () => { - toast("Project is being created !", { - description: "", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) - }, - onError: (error) => { - toast("Project creation has failed !", { - description: error as any, - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) - }, - onSuccess: (data) => { - queryClient.setQueryData(['projects'], (oldQueryData = []) => { - return [...oldQueryData, data]; - }); - toast("Project has been created !", { - description: "", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) - }, - onSettled: () => { - }, - }); + const createProjectsPromise = (data: IProDto) => { + return new Promise(async (resolve, reject) => { + try { + const result = await add(data); + resolve(result); + + } catch (error) { + reject(error); + } + }); + }; + return { + mutationFn: useMutation({ + mutationFn: add, + }), + createProjectsPromise + } }; export default useCreateProject; diff --git a/apps/client-ts/src/hooks/create/useCreateUser.tsx b/apps/client-ts/src/hooks/create/useCreateUser.tsx index 719af603a..a4ac462b2 100644 --- a/apps/client-ts/src/hooks/create/useCreateUser.tsx +++ b/apps/client-ts/src/hooks/create/useCreateUser.tsx @@ -1,6 +1,5 @@ import config from '@/lib/config'; -import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { toast } from "sonner" +import { useMutation } from '@tanstack/react-query'; interface IUserDto { first_name: string, @@ -11,7 +10,6 @@ interface IUserDto { id_organisation?: string, } const useCreateUser = () => { - const add = async (userData: IUserDto) => { // Fetch the token const response = await fetch(`${config.API_URL}/auth/register`, { @@ -28,39 +26,24 @@ const useCreateUser = () => { return response.json(); }; - return useMutation({ - mutationFn: add, - onMutate: () => { - toast("User is being created !", { - description: "", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) - }, - onError: (error) => { - toast("User generation failed !", { - description: error as any, - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) - }, - onSuccess: (data) => { - - toast("User has been generated !", { - description: "", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) - }, - onSettled: () => { - }, - }); + const createUserPromise = (data: IUserDto) => { + return new Promise(async (resolve, reject) => { + try { + const result = await add(data); + resolve(result); + + } catch (error) { + reject(error); + } + }); + }; + return { + mutationFn: useMutation({ + mutationFn: add, + }), + createUserPromise + } + }; export default useCreateUser; diff --git a/apps/client-ts/src/hooks/create/useCreateWebhook.tsx b/apps/client-ts/src/hooks/create/useCreateWebhook.tsx index 90327d603..8bb644674 100644 --- a/apps/client-ts/src/hooks/create/useCreateWebhook.tsx +++ b/apps/client-ts/src/hooks/create/useCreateWebhook.tsx @@ -1,6 +1,5 @@ import config from '@/lib/config'; -import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { toast } from "sonner" +import { useMutation } from '@tanstack/react-query'; import Cookies from 'js-cookie'; interface IWebhookDto { @@ -10,7 +9,6 @@ interface IWebhookDto { scope: string[]; } const useCreateWebhook = () => { - const queryClient = useQueryClient(); const add = async (data: IWebhookDto) => { const response = await fetch(`${config.API_URL}/webhook`, { method: 'POST', @@ -27,41 +25,25 @@ const useCreateWebhook = () => { return response.json(); }; - return useMutation({ - mutationFn: add, - onMutate: () => { - toast("Webhook endpoint has been created !", { - description: "", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) - }, - onError: (error) => { - toast("Webhook endpoint creation has failed !", { - description: error as any, - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) - }, - onSuccess: (data) => { - queryClient.setQueryData(['webhooks'], (oldQueryData = []) => { - return [...oldQueryData, data]; - }); - toast("Webhook endpoint has been created! ", { - description: "", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) - }, - onSettled: () => { - }, - }); + + const createWebhookPromise = (data: IWebhookDto) => { + return new Promise(async (resolve, reject) => { + try { + const result = await add(data); + resolve(result); + + } catch (error) { + reject(error); + } + }); + }; + + return { + mutationFn: useMutation({ + mutationFn: add, + }), + createWebhookPromise + } }; export default useCreateWebhook; diff --git a/apps/client-ts/src/hooks/create/useDefineField.tsx b/apps/client-ts/src/hooks/create/useDefineField.tsx index 191c578ca..985a39c57 100644 --- a/apps/client-ts/src/hooks/create/useDefineField.tsx +++ b/apps/client-ts/src/hooks/create/useDefineField.tsx @@ -1,6 +1,5 @@ import config from '@/lib/config'; -import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { toast } from "sonner" +import { useMutation } from '@tanstack/react-query'; import Cookies from 'js-cookie'; interface IDefineTargetFieldDto{ @@ -11,8 +10,6 @@ interface IDefineTargetFieldDto{ } const useDefineField = () => { - const queryClient = useQueryClient() - const define = async (data: IDefineTargetFieldDto) => { const response = await fetch(`${config.API_URL}/field-mappings/define`, { method: 'POST', @@ -29,41 +26,24 @@ const useDefineField = () => { return response.json(); }; - return useMutation({ - mutationFn: define, - onMutate: () => { - toast("Field mapping is being defined !", { - description: "", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) - }, - onError: (error) => { - toast("Field mapping definition failed !", { - description: error as any, - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) - }, - onSuccess: (data) => { - queryClient.setQueryData(['mappings'], (oldQueryData = []) => { - return [...oldQueryData, data]; - }); - toast("Field mapping has been defined !", { - description: "", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) - }, - onSettled: () => { - }, - }); + + const defineMappingPromise = (data: IDefineTargetFieldDto) => { + return new Promise(async (resolve, reject) => { + try { + const result = await define(data); + resolve(result); + + } catch (error) { + reject(error); + } + }); + }; + return { + mutationFn: useMutation({ + mutationFn: define, + }), + defineMappingPromise + } }; export default useDefineField; diff --git a/apps/client-ts/src/hooks/create/useMapField.tsx b/apps/client-ts/src/hooks/create/useMapField.tsx index 05b319597..019d5a069 100644 --- a/apps/client-ts/src/hooks/create/useMapField.tsx +++ b/apps/client-ts/src/hooks/create/useMapField.tsx @@ -1,6 +1,5 @@ import config from '@/lib/config'; import { useMutation } from '@tanstack/react-query'; -import { toast } from "sonner" import Cookies from 'js-cookie'; interface IMapTargetFieldDto { @@ -27,39 +26,23 @@ const useMapField = () => { return response.json(); }; - return useMutation({ - mutationFn: map, - onMutate: () => { - toast("Field is being mapped !", { - description: "", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) - }, - onError: (error) => { - toast("Field mapping failed !", { - description: error as any, - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) - - }, - onSuccess: () => { - toast("Field has been mapped !", { - description: "", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) - }, - onSettled: () => { - }, - }); + const mapMappingPromise = (data: IMapTargetFieldDto) => { + return new Promise(async (resolve, reject) => { + try { + const result = await map(data); + resolve(result); + + } catch (error) { + reject(error); + } + }); + }; + return { + mutationFn: useMutation({ + mutationFn: map, + }), + mapMappingPromise + } }; export default useMapField; diff --git a/apps/client-ts/src/hooks/create/useRefreshAccessToken.tsx b/apps/client-ts/src/hooks/create/useRefreshAccessToken.tsx index b711bb00d..bedea3d80 100644 --- a/apps/client-ts/src/hooks/create/useRefreshAccessToken.tsx +++ b/apps/client-ts/src/hooks/create/useRefreshAccessToken.tsx @@ -1,6 +1,5 @@ import config from '@/lib/config'; import { useMutation } from '@tanstack/react-query'; -import { toast } from "sonner" import Cookies from 'js-cookie'; interface IRefreshOutputDto { @@ -28,23 +27,10 @@ const useRefreshAccessToken = () => { }; return useMutation({ mutationFn: refreshAccessToken, - onMutate: () => { - }, - onError: (error) => { - toast.error("Refreshing token generation failed !", { - description: error as any, - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) - }, onSuccess: (data : IRefreshOutputDto) => { Cookies.remove('access_token'); Cookies.set('access_token', data.access_token, {expires:1}); }, - onSettled: () => { - }, }); }; diff --git a/apps/client-ts/src/hooks/delete/useDeleteApiKey.tsx b/apps/client-ts/src/hooks/delete/useDeleteApiKey.tsx index a08e99463..94c8a2459 100644 --- a/apps/client-ts/src/hooks/delete/useDeleteApiKey.tsx +++ b/apps/client-ts/src/hooks/delete/useDeleteApiKey.tsx @@ -1,7 +1,5 @@ import config from '@/lib/config'; -import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { toast } from "sonner" -import { api_keys as ApiKey } from 'api'; +import { useMutation } from '@tanstack/react-query'; import Cookies from 'js-cookie'; interface IApiKeyDto { @@ -9,8 +7,6 @@ interface IApiKeyDto { } const useDeleteApiKey = () => { - const queryClient = useQueryClient(); - const remove = async (apiKeyData: IApiKeyDto) => { const response = await fetch(`${config.API_URL}/auth/api-keys/${apiKeyData.id_api_key}`, { method: 'DELETE', @@ -25,41 +21,24 @@ const useDeleteApiKey = () => { } return response.json(); }; - return useMutation({ - mutationFn: remove, - onMutate: () => { - toast("api key is being deleted !", { - description: "", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) - }, - onError: (error) => { - toast("The deleting of api key has failed !", { - description: error as any, - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) - }, - onSuccess: (data: ApiKey) => { - queryClient.setQueryData(['api-keys'], (oldQueryData = []) => { - return oldQueryData.filter((api_key) => api_key.id_api_key !== data.id_api_key); - }); - toast("Api Key has been deleted successfully!", { - description: "The api key has been removed from your list.", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }); - }, - onSettled: () => { - }, - }); + + const deleteApiKeyPromise = (data: IApiKeyDto) => { + return new Promise(async (resolve, reject) => { + try { + const result = await remove(data); + resolve(result); + + } catch (error) { + reject(error); + } + }); + }; + return { + mutate: useMutation({ + mutationFn: remove, + }), + deleteApiKeyPromise, + }; }; export default useDeleteApiKey; diff --git a/apps/client-ts/src/hooks/delete/useDeleteConnectionStrategy.tsx b/apps/client-ts/src/hooks/delete/useDeleteConnectionStrategy.tsx index 140b36a32..64304e31a 100644 --- a/apps/client-ts/src/hooks/delete/useDeleteConnectionStrategy.tsx +++ b/apps/client-ts/src/hooks/delete/useDeleteConnectionStrategy.tsx @@ -1,16 +1,11 @@ import config from '@/lib/config'; -import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { toast } from "sonner" -import { connection_strategies as ConnectionStrategies } from 'api'; +import { useMutation } from '@tanstack/react-query'; import Cookies from 'js-cookie'; - interface IDeleteConnectionStrategyDto { id_cs: string } -const useDeleteConnectionStrategy = () => { - const queryClient = useQueryClient(); - +const useDeleteConnectionStrategy = () => { const remove = async (connectionStrategyData: IDeleteConnectionStrategyDto) => { const response = await fetch(`${config.API_URL}/connections-strategies/delete`, { method: 'POST', @@ -27,42 +22,31 @@ const useDeleteConnectionStrategy = () => { return response.json(); }; - return useMutation({ - mutationFn: remove, - onMutate: () => { - toast("Connection Strategy is being deleted !", { - description: "", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) - }, - onError: (error) => { - toast("The deleting of Connection Strategy has failed !", { - description: error as any, - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) - }, - onSuccess: (data : ConnectionStrategies) => { - console.log("After Delete Data Received : ",JSON.stringify(data)) - queryClient.setQueryData(['connection-strategies'], (oldQueryData = []) => { + + const deleteCsPromise = (data: IDeleteConnectionStrategyDto) => { + return new Promise(async (resolve, reject) => { + try { + const result = await remove(data); + resolve(result); + + } catch (error) { + reject(error); + } + }); + }; + /* + queryClient.setQueryData(['connection-strategies'], (oldQueryData = []) => { return oldQueryData.filter((CS) => CS.id_connection_strategy !== data.id_connection_strategy) }); - toast("Connection Strategy has been deleted !", { - description: "", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) - }, - onSettled: () => { - }, - }); + */ + + return { + mutate: useMutation({ + mutationFn: remove, + }), + deleteCsPromise, + }; + }; export default useDeleteConnectionStrategy; diff --git a/apps/client-ts/src/hooks/delete/useDeleteWebhook.tsx b/apps/client-ts/src/hooks/delete/useDeleteWebhook.tsx index a33c643c5..984d07d2d 100644 --- a/apps/client-ts/src/hooks/delete/useDeleteWebhook.tsx +++ b/apps/client-ts/src/hooks/delete/useDeleteWebhook.tsx @@ -1,7 +1,5 @@ import config from '@/lib/config'; -import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { toast } from "sonner" -import { webhook_endpoints as Webhook } from 'api'; +import { useMutation } from '@tanstack/react-query'; import Cookies from 'js-cookie'; interface IWebhookDto { @@ -9,8 +7,6 @@ interface IWebhookDto { } const useDeleteWebhook = () => { - const queryClient = useQueryClient(); - const remove = async (webhookData: IWebhookDto) => { const response = await fetch(`${config.API_URL}/webhook/${webhookData.id_webhook}`, { method: 'DELETE', @@ -25,41 +21,25 @@ const useDeleteWebhook = () => { } return response.json(); }; - return useMutation({ - mutationFn: remove, - onMutate: () => { - toast("api key is being deleted !", { - description: "", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) - }, - onError: (error) => { - toast("The deleting of api key has failed !", { - description: error as any, - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) - }, - onSuccess: (data: Webhook) => { - queryClient.setQueryData(['webhooks'], (oldQueryData = []) => { - return oldQueryData.filter((wh) => wh.id_webhook_endpoint !== data.id_webhook_endpoint); - }); - toast("Api Key has been deleted successfully!", { - description: "The api key has been removed from your list.", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }); - }, - onSettled: () => { - }, - }); + + const deleteWebhookPromise = (data: IWebhookDto) => { + return new Promise(async (resolve, reject) => { + try { + const result = await remove(data); + resolve(result); + + } catch (error) { + reject(error); + } + }); + }; + + return { + mutate: useMutation({ + mutationFn: remove, + }), + deleteWebhookPromise, + }; }; export default useDeleteWebhook; diff --git a/apps/client-ts/src/hooks/get/useConnectionStrategyAuthCredentials.tsx b/apps/client-ts/src/hooks/get/useConnectionStrategyAuthCredentials.tsx index 31a0190ab..5c28ed68c 100644 --- a/apps/client-ts/src/hooks/get/useConnectionStrategyAuthCredentials.tsx +++ b/apps/client-ts/src/hooks/get/useConnectionStrategyAuthCredentials.tsx @@ -27,13 +27,13 @@ const useConnectionStrategyAuthCredentials = () => { return useMutation({ mutationFn: getCSCredentials, onError: (error) => { - toast("The CS credentials fetching failed !", { + /*toast("The CS credentials fetching failed !", { description: error as any, action: { label: "Close", onClick: () => console.log("Close"), }, - }) + })*/ }, onSuccess: (data) => { return data diff --git a/apps/client-ts/src/hooks/get/useProjectConnectors.tsx b/apps/client-ts/src/hooks/get/useProjectConnectors.tsx new file mode 100644 index 000000000..540c9c8c5 --- /dev/null +++ b/apps/client-ts/src/hooks/get/useProjectConnectors.tsx @@ -0,0 +1,34 @@ +import config from '@/lib/config'; +import { useQuery } from '@tanstack/react-query'; +import Cookies from 'js-cookie'; + +interface ProjectConnectorBase { + id_project: string; + id_project_connector: string; +} + +interface ProjectConnector extends ProjectConnectorBase { + [key: string]: boolean | string; +} + +const useProjectConnectors = (id: string) => { + return useQuery({ + queryKey: ['project-connectors'], + queryFn: async (): Promise => { + const response = await fetch(`${config.API_URL}/project-connectors?projectId=${id}`, + { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${Cookies.get('access_token')}`, + }, + }); + if (!response.ok) { + throw new Error('Network response was not ok'); + } + return response.json(); + } + }); +}; + +export default useProjectConnectors; diff --git a/apps/client-ts/src/hooks/get/useUser.tsx b/apps/client-ts/src/hooks/get/useUser.tsx index 352dffcf2..21ecf9f91 100644 --- a/apps/client-ts/src/hooks/get/useUser.tsx +++ b/apps/client-ts/src/hooks/get/useUser.tsx @@ -35,34 +35,18 @@ const useUser = () => { }; return useMutation({ mutationFn: getUser, - onMutate: () => { - // toast("Fetching the user !", { - // description: "", - // action: { - // label: "Close", - // onClick: () => console.log("Close"), - // }, - // }) - }, onError: (error) => { Cookies.remove('access_token') - toast.error("Fetch User failed !", { + /*toast.error("Fetch User failed !", { description: error as any, action: { label: "Close", onClick: () => console.log("Close"), }, - }) + })*/ }, onSuccess: (data : IUserDto) => { setProfile(data); - toast.success("User has been fetched !", { - description: "", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) }, onSettled: () => { }, diff --git a/apps/client-ts/src/hooks/update/useUpdateConnectionStrategy.tsx b/apps/client-ts/src/hooks/update/useUpdateConnectionStrategy.tsx index 15c6b3a9f..cd32638ba 100644 --- a/apps/client-ts/src/hooks/update/useUpdateConnectionStrategy.tsx +++ b/apps/client-ts/src/hooks/update/useUpdateConnectionStrategy.tsx @@ -1,9 +1,6 @@ import config from '@/lib/config'; -import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { toast } from "sonner" -import { connection_strategies as ConnectionStrategies } from 'api'; +import { useMutation } from '@tanstack/react-query'; import Cookies from 'js-cookie'; - interface IUpdateConnectionStrategyDto { id_cs?: string updateToggle: boolean, @@ -15,9 +12,7 @@ interface IUpdateConnectionStrategyDto { } -const useUpdateWebhook = () => { - const queryClient = useQueryClient(); - +const useUpdateConnectionStrategy = () => { const update = async (connectionStrategyData: IUpdateConnectionStrategyDto) => { if(connectionStrategyData.updateToggle) { console.log(connectionStrategyData.id_cs) @@ -57,41 +52,25 @@ const useUpdateWebhook = () => { } }; - return useMutation({ - mutationFn: update, - onMutate: () => { - toast("Connection Strategy is being updated !", { - description: "", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) - }, - onError: (error) => { - toast("The updating of Connection Strategy has failed !", { - description: error as any, - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) - }, - onSuccess: (data : ConnectionStrategies) => { - queryClient.setQueryData(['connection-strategies'], (oldQueryData = []) => { - return oldQueryData.map((CS) => CS.id_connection_strategy === data.id_connection_strategy ? data : CS) - }); - toast("Connection Strategy has been updated !", { - description: "", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) - }, - onSettled: () => { - }, - }); + + const updateCsPromise = (data: IUpdateConnectionStrategyDto) => { + return new Promise(async (resolve, reject) => { + try { + const result = await update(data); + resolve(result); + + } catch (error) { + reject(error); + } + }); + }; + return { + mutate: useMutation({ + mutationFn: update, + }), + updateCsPromise, + }; + }; -export default useUpdateWebhook; +export default useUpdateConnectionStrategy; diff --git a/apps/client-ts/src/hooks/update/useUpdateProjectConnectors.tsx b/apps/client-ts/src/hooks/update/useUpdateProjectConnectors.tsx new file mode 100644 index 000000000..219884810 --- /dev/null +++ b/apps/client-ts/src/hooks/update/useUpdateProjectConnectors.tsx @@ -0,0 +1,49 @@ +import config from '@/lib/config'; +import { useMutation } from '@tanstack/react-query'; +import Cookies from 'js-cookie'; + +interface IUpdateProjectConnectorsDto { + column: string; + status: boolean; +} + +const useUpdateProjectConnectors = () => { + const update = async (data: IUpdateProjectConnectorsDto) => { + const response = await fetch(`${config.API_URL}/project-connectors`, { + method: 'POST', + body: JSON.stringify(data), + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${Cookies.get('access_token')}`, + }, + }); + + if (!response.ok) { + throw new Error('Failed to update catalog option'); + } + + return response.json(); + + }; + + const updateProjectConnectorPromise = (data: IUpdateProjectConnectorsDto) => { + return new Promise(async (resolve, reject) => { + try { + const result = await update(data); + resolve(result); + + } catch (error) { + reject(error); + } + }); + }; + + return { + mutate: useMutation({ + mutationFn: update, + }), + updateProjectConnectorPromise, + }; +}; + +export default useUpdateProjectConnectors; diff --git a/apps/client-ts/src/hooks/update/useUpdateWebhookStatus.tsx b/apps/client-ts/src/hooks/update/useUpdateWebhookStatus.tsx index d6cafebbc..de57151db 100644 --- a/apps/client-ts/src/hooks/update/useUpdateWebhookStatus.tsx +++ b/apps/client-ts/src/hooks/update/useUpdateWebhookStatus.tsx @@ -1,6 +1,5 @@ import config from '@/lib/config'; import { useMutation } from '@tanstack/react-query'; -import { toast } from "sonner" import Cookies from 'js-cookie'; interface IWebhookUpdateDto { @@ -24,18 +23,25 @@ const useUpdateWebhookStatus = () => { return response.json(); }; - return useMutation({ - mutationFn: update, - onError: (error) => { - toast("Webhook endpoint update has failed !", { - description: error as any, - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) - }, - }); + + const updateWebhookPromise = (data: IWebhookUpdateDto) => { + return new Promise(async (resolve, reject) => { + try { + const result = await update(data); + resolve(result); + + } catch (error) { + reject(error); + } + }); + }; + + return { + mutate: useMutation({ + mutationFn: update, + }), + updateWebhookPromise, + }; }; export default useUpdateWebhookStatus; diff --git a/apps/client-ts/src/hooks/use-callback-ref.ts b/apps/client-ts/src/hooks/use-callback-ref.ts new file mode 100644 index 000000000..873db5830 --- /dev/null +++ b/apps/client-ts/src/hooks/use-callback-ref.ts @@ -0,0 +1,27 @@ +import * as React from "react" + +/** + * @see https://github.com/radix-ui/primitives/blob/main/packages/react/use-callback-ref/src/useCallbackRef.tsx + */ + +/** + * A custom hook that converts a callback to a ref to avoid triggering re-renders when passed as a + * prop or avoid re-executing effects when passed as a dependency + */ +function useCallbackRef unknown>( + callback: T | undefined +): T { + const callbackRef = React.useRef(callback) + + React.useEffect(() => { + callbackRef.current = callback + }) + + // https://github.com/facebook/react/issues/19240 + return React.useMemo( + () => ((...args) => callbackRef.current?.(...args)) as T, + [] + ) +} + +export { useCallbackRef } \ No newline at end of file diff --git a/apps/client-ts/src/hooks/use-controllable-state.ts b/apps/client-ts/src/hooks/use-controllable-state.ts new file mode 100644 index 000000000..ff3cc3cc8 --- /dev/null +++ b/apps/client-ts/src/hooks/use-controllable-state.ts @@ -0,0 +1,67 @@ +import * as React from "react" + +import { useCallbackRef } from "./use-callback-ref" + +/** + * @see https://github.com/radix-ui/primitives/blob/main/packages/react/use-controllable-state/src/useControllableState.tsx + */ + +type UseControllableStateParams = { + prop?: T | undefined + defaultProp?: T | undefined + onChange?: (state: T) => void +} + +type SetStateFn = (prevState?: T) => T + +function useControllableState({ + prop, + defaultProp, + onChange = () => {}, +}: UseControllableStateParams) { + const [uncontrolledProp, setUncontrolledProp] = useUncontrolledState({ + defaultProp, + onChange, + }) + const isControlled = prop !== undefined + const value = isControlled ? prop : uncontrolledProp + const handleChange = useCallbackRef(onChange) + + const setValue: React.Dispatch> = + React.useCallback( + (nextValue) => { + if (isControlled) { + const setter = nextValue as SetStateFn + const value = + typeof nextValue === "function" ? setter(prop) : nextValue + if (value !== prop) handleChange(value as T) + } else { + setUncontrolledProp(nextValue) + } + }, + [isControlled, prop, setUncontrolledProp, handleChange] + ) + + return [value, setValue] as const +} + +function useUncontrolledState({ + defaultProp, + onChange, +}: Omit, "prop">) { + const uncontrolledState = React.useState(defaultProp) + const [value] = uncontrolledState + const prevValueRef = React.useRef(value) + const handleChange = useCallbackRef(onChange) + + React.useEffect(() => { + if (prevValueRef.current !== value) { + handleChange(value as T) + prevValueRef.current = value + } + }, [value, prevValueRef, handleChange]) + + return uncontrolledState +} + +export { useControllableState } \ No newline at end of file diff --git a/apps/client-ts/src/lib/utils.ts b/apps/client-ts/src/lib/utils.ts index 596760aa2..5f01dfee5 100644 --- a/apps/client-ts/src/lib/utils.ts +++ b/apps/client-ts/src/lib/utils.ts @@ -27,6 +27,24 @@ export function formatISODate(ISOString: string): string { return formatter.format(date); } +export function formatBytes( + bytes: number, + opts: { + decimals?: number + sizeType?: "accurate" | "normal" + } = {} +) { + const { decimals = 0, sizeType = "normal" } = opts + + const sizes = ["Bytes", "KB", "MB", "GB", "TB"] + const accurateSizes = ["Bytes", "KiB", "MiB", "GiB", "TiB"] + if (bytes === 0) return "0 Byte" + const i = Math.floor(Math.log(bytes) / Math.log(1024)) + return `${(bytes / Math.pow(1024, i)).toFixed(decimals)} ${ + sizeType === "accurate" ? accurateSizes[i] ?? "Bytest" : sizes[i] ?? "Bytes" + }` +} + export function truncateMiddle(str: string, maxLength: number) { if (str.length <= maxLength) { diff --git a/apps/client-ts/tailwind.config.ts b/apps/client-ts/tailwind.config.ts index 84287e82f..79e700a58 100644 --- a/apps/client-ts/tailwind.config.ts +++ b/apps/client-ts/tailwind.config.ts @@ -74,6 +74,17 @@ const config = { }, }, }, + extend: { + keyframes: { + "shine": { + from: { backgroundPosition: '200% 0' }, + to: { backgroundPosition: '-200% 0' }, + }, + }, + animation: { + "shine": "shine 8s ease-in-out infinite", + }, + }, plugins: [require("tailwindcss-animate")], } satisfies Config diff --git a/apps/embedded-catalog/react/package.json b/apps/embedded-catalog/react/package.json index 6bd59905e..e8b76f32d 100644 --- a/apps/embedded-catalog/react/package.json +++ b/apps/embedded-catalog/react/package.json @@ -4,7 +4,8 @@ "description": "", "main": "dist/index.js", "scripts": { - "build": "tsup" + "build": "tsup", + "start": "node dist/index.js" }, "keywords": [], "author": "", @@ -21,12 +22,17 @@ }, "dependencies": { "@panora/shared": "workspace:^", + "@radix-ui/react-icons": "^1.3.0", + "@radix-ui/react-slot": "^1.0.2", "@tanstack/react-query": "^5.12.2", - "react-loader-spinner": "^5.4.5" + "class-variance-authority": "^0.7.0", + "clsx": "^2.1.0", + "react-loader-spinner": "^5.4.5", + "tailwind-merge": "^2.2.1" }, "peerDependencies": { + "@tanstack/react-query": "^5.12.2", "react": "^18.2.0", - "react-dom": "^18.2.0", - "@tanstack/react-query": "^5.12.2" + "react-dom": "^18.2.0" } } diff --git a/apps/embedded-catalog/react/src/components/PanoraDynamicCatalog.tsx b/apps/embedded-catalog/react/src/components/PanoraDynamicCatalog.tsx new file mode 100644 index 000000000..426cbfa24 --- /dev/null +++ b/apps/embedded-catalog/react/src/components/PanoraDynamicCatalog.tsx @@ -0,0 +1,145 @@ +import {useState,useEffect} from 'react' +import {providersArray, ConnectorCategory, categoryFromSlug, Provider} 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'; + +export interface DynamicCardProp { + projectId: string; + returnUrl: string; + linkedUserId: string; + category?: ConnectorCategory; + optionalApiUrl?: string, +} + +const DynamicCatalog = ({projectId,returnUrl,linkedUserId, category, optionalApiUrl} : DynamicCardProp) => { + + // by default we render all integrations but if category is provided we filter by category + + const [selectedProvider, setSelectedProvider] = useState<{ + provider: string; + category: string; + }>(); + + const [loading, setLoading] = useState<{ + status: boolean; provider: string + }>({status: false, provider: ''}); + + const [error,setError] = useState(false); + const [startFlow, setStartFlow] = useState(false); + + const [data, setData] = useState([]); + + const { open, isReady } = useOAuth({ + providerName: selectedProvider?.provider!, + vertical: selectedProvider?.category! as ConnectorCategory, + returnUrl: returnUrl, + projectId: projectId, + linkedUserId: linkedUserId, + onSuccess: () => console.log('OAuth successful'), + }); + + const {data: connectorsForProject} = useProjectConnectors(projectId); + + const onWindowClose = () => { + setSelectedProvider({ + provider: '', + category: '' + }); + setLoading({ + status: false, + provider: '' + }) + setStartFlow(false); + } + + useEffect(() => { + if (startFlow && isReady) { + open(onWindowClose); + } else if (startFlow && !isReady) { + setLoading({ + status: false, + provider: '' + }); + } + }, [startFlow, isReady, open]); + + useEffect(()=>{ + const PROVIDERS = !category ? providersArray() : providersArray(category); + 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()) + } + }, [connectorsForProject, category]) + + const handleStartFlow = (walletName: string, category: string) => { + setSelectedProvider({provider: walletName.toLowerCase(), category: category.toLowerCase()}); + setLoading({status: true, provider: selectedProvider?.provider!}); + setStartFlow(true); + } + + function transformConnectorsStatus(connectors : {[key: string]: boolean}): { connector_name: string;category: string; status: string }[] { + return Object.entries(connectors).flatMap(([key, value]) => { + const [category_slug, connector_name] = key.split('_').map((part: string) => part.trim()); + const category = categoryFromSlug(category_slug); + if (category != null) { + return [{ + connector_name: connector_name, + category: category, + status: String(value) + }]; + } + return []; + }); + } + + + return ( +
+ {data && data.map((item) => { + return ( + +
+
+
+ +
{`${item.name.substring(0, 1).toUpperCase()}${item.name.substring(1)}`}
+
+
+ {item.description!.substring(0, 300)} +
+
+ +
+ +
+
+
+
+
+ )}) + } +
+ ) +} + + +export default DynamicCatalog \ No newline at end of file diff --git a/apps/embedded-catalog/react/src/components/PanoraIntegrationCard.tsx b/apps/embedded-catalog/react/src/components/PanoraIntegrationCard.tsx new file mode 100644 index 000000000..5b385f52f --- /dev/null +++ b/apps/embedded-catalog/react/src/components/PanoraIntegrationCard.tsx @@ -0,0 +1,87 @@ +import useOAuth from '@/hooks/useOAuth'; +import { useEffect, useState } from 'react'; +import { CONNECTORS_METADATA, ConnectorCategory } from '@panora/shared'; +import { Button } from './ui/button2'; +import { Card } from './ui/card'; +import { ArrowRightIcon } from '@radix-ui/react-icons'; + +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 [loading, setLoading] = useState(false) + const [startFlow, setStartFlow] = useState(false); + + const { open, isReady } = useOAuth({ + providerName: name.toLowerCase(), + vertical: category.toLowerCase() as ConnectorCategory, + returnUrl: returnUrl, + projectId: projectId, + linkedUserId: linkedUserId, + optionalApiUrl: optionalApiUrl, + onSuccess: () => console.log('OAuth successful'), + }); + + const onWindowClose = () => { + setLoading(false); + return; + } + + useEffect(() => { + if (startFlow && isReady) { + open(onWindowClose); + } else if (startFlow && !isReady) { + setLoading(false); + } + }, [startFlow, isReady, open]); + + + const handleStartFlow = () => { + setLoading(true); + setStartFlow(true); + } + + const CONNECTOR = CONNECTORS_METADATA[category!.toLowerCase()][name.toLowerCase()] + + const img = CONNECTOR.logoPath; + + + return ( +
+ +
+
+
+ +
{`${name.substring(0, 1).toUpperCase()}${name.substring(1)}`}
+
+
+ {CONNECTOR.description!.substring(0, 300)} +
+
+ +
+ +
+
+
+
+
+
+ ) + }; + + + +export default PanoraIntegrationCard; \ No newline at end of file diff --git a/apps/embedded-catalog/react/src/components/ui/badge.tsx b/apps/embedded-catalog/react/src/components/ui/badge.tsx new file mode 100644 index 000000000..f000e3ef5 --- /dev/null +++ b/apps/embedded-catalog/react/src/components/ui/badge.tsx @@ -0,0 +1,36 @@ +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const badgeVariants = cva( + "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", + { + variants: { + variant: { + default: + "border-transparent bg-primary text-primary-foreground hover:bg-primary/80", + secondary: + "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", + destructive: + "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", + outline: "text-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +export interface BadgeProps + extends React.HTMLAttributes, + VariantProps {} + +function Badge({ className, variant, ...props }: BadgeProps) { + return ( +
+ ) +} + +export { Badge, badgeVariants } diff --git a/apps/embedded-catalog/react/src/components/ui/button.tsx b/apps/embedded-catalog/react/src/components/ui/button.tsx new file mode 100644 index 000000000..0ba427735 --- /dev/null +++ b/apps/embedded-catalog/react/src/components/ui/button.tsx @@ -0,0 +1,56 @@ +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const buttonVariants = cva( + "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground hover:bg-primary/90", + destructive: + "bg-destructive text-destructive-foreground hover:bg-destructive/90", + outline: + "border border-input bg-background hover:bg-accent hover:text-accent-foreground", + secondary: + "bg-secondary text-secondary-foreground hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-10 px-4 py-2", + sm: "h-9 rounded-md px-3", + lg: "h-11 rounded-md px-8", + icon: "h-10 w-10", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +) + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button" + return ( + + ) + } +) +Button.displayName = "Button" + +export { Button, buttonVariants } diff --git a/apps/embedded-catalog/react/src/components/ui/button2.tsx b/apps/embedded-catalog/react/src/components/ui/button2.tsx new file mode 100644 index 000000000..b1f45e2fc --- /dev/null +++ b/apps/embedded-catalog/react/src/components/ui/button2.tsx @@ -0,0 +1,108 @@ +import * as React from "react"; +import { Slot, Slottable } from "@radix-ui/react-slot"; +import { cva, type VariantProps } from "class-variance-authority"; +import { cn } from "@/lib/utils"; + +const buttonVariants = cva( + "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground hover:bg-primary/90", + destructive: + "bg-destructive text-destructive-foreground hover:bg-destructive/90", + outline: + "border border-input bg-background hover:bg-accent hover:text-accent-foreground", + secondary: + "bg-secondary text-secondary-foreground hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", + expandIcon: + "group relative text-primary-foreground bg-primary hover:bg-primary/90", + ringHover: + "bg-primary text-primary-foreground transition-all duration-300 hover:bg-primary/90 hover:ring-2 hover:ring-primary/90 hover:ring-offset-2", + shine: + "text-primary-foreground animate-shine bg-gradient-to-r from-primary via-primary/75 to-primary bg-[length:400%_100%] ", + gooeyRight: + "text-primary-foreground relative bg-primary z-0 overflow-hidden transition-all duration-500 before:absolute before:inset-0 before:-z-10 before:translate-x-[150%] before:translate-y-[150%] before:scale-[2.5] before:rounded-[100%] before:bg-gradient-to-r from-zinc-400 before:transition-transform before:duration-1000 hover:before:translate-x-[0%] hover:before:translate-y-[0%] ", + gooeyLeft: + "text-primary-foreground relative bg-primary z-0 overflow-hidden transition-all duration-500 after:absolute after:inset-0 after:-z-10 after:translate-x-[-150%] after:translate-y-[150%] after:scale-[2.5] after:rounded-[100%] after:bg-gradient-to-l from-zinc-400 after:transition-transform after:duration-1000 hover:after:translate-x-[0%] hover:after:translate-y-[0%] ", + linkHover1: + "relative after:absolute after:bg-primary after:bottom-2 after:h-[1px] after:w-2/3 after:origin-bottom-left after:scale-x-100 hover:after:origin-bottom-right hover:after:scale-x-0 after:transition-transform after:ease-in-out after:duration-300", + linkHover2: + "relative after:absolute after:bg-primary after:bottom-2 after:h-[1px] after:w-2/3 after:origin-bottom-right after:scale-x-0 hover:after:origin-bottom-left hover:after:scale-x-100 after:transition-transform after:ease-in-out after:duration-300", + }, + size: { + default: "h-10 px-4 py-2", + sm: "h-9 rounded-md px-3", + lg: "h-11 rounded-md px-8", + icon: "h-10 w-10", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +); + +interface IconProps { + Icon: React.ElementType; + iconPlacement: "left" | "right"; +} + +interface IconRefProps { + Icon?: never; + iconPlacement?: undefined; +} + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean; +} + +export type ButtonIconProps = IconProps | IconRefProps; + +const Button = React.forwardRef< + HTMLButtonElement, + ButtonProps & ButtonIconProps +>( + ( + { + className, + variant, + size, + asChild = false, + Icon, + iconPlacement, + ...props + }, + ref + ) => { + const Comp = asChild ? Slot : "button"; + return ( + + {Icon && iconPlacement === "left" && ( +
+ +
+ )} + {props.children} + {Icon && iconPlacement === "right" && ( +
+ +
+ )} +
+ ); + } +); +Button.displayName = "Button"; + +export { Button, buttonVariants }; + \ No newline at end of file diff --git a/apps/embedded-catalog/react/src/components/ui/card.tsx b/apps/embedded-catalog/react/src/components/ui/card.tsx new file mode 100644 index 000000000..afa13ecfa --- /dev/null +++ b/apps/embedded-catalog/react/src/components/ui/card.tsx @@ -0,0 +1,79 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +const Card = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +Card.displayName = "Card" + +const CardHeader = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardHeader.displayName = "CardHeader" + +const CardTitle = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)) +CardTitle.displayName = "CardTitle" + +const CardDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)) +CardDescription.displayName = "CardDescription" + +const CardContent = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)) +CardContent.displayName = "CardContent" + +const CardFooter = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardFooter.displayName = "CardFooter" + +export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } diff --git a/apps/embedded-catalog/react/src/components/ui/loading-spinner.tsx b/apps/embedded-catalog/react/src/components/ui/loading-spinner.tsx new file mode 100644 index 000000000..db4a2b4ac --- /dev/null +++ b/apps/embedded-catalog/react/src/components/ui/loading-spinner.tsx @@ -0,0 +1,19 @@ +import { cn } from "@/lib/utils" + +export const LoadingSpinner = ({className} : {className?: string}) => { + return ( + + + ) +} \ No newline at end of file diff --git a/apps/embedded-catalog/react/src/global.css b/apps/embedded-catalog/react/src/global.css index b5c61c956..6e066f761 100644 --- a/apps/embedded-catalog/react/src/global.css +++ b/apps/embedded-catalog/react/src/global.css @@ -1,3 +1,39 @@ @tailwind base; @tailwind components; @tailwind utilities; + +@layer utilities { + /* Chrome, Safari and Opera */ + .no-scrollbar::-webkit-scrollbar { + scrollbar-face-color: #646464; + scrollbar-base-color: #646464; + scrollbar-3dlight-color: #646464; + scrollbar-highlight-color: #646464; + scrollbar-track-color: #000; + scrollbar-arrow-color: #000; + scrollbar-shadow-color: #646464; + scrollbar-dark-shadow-color: #646464; + } + + .no-scrollbar { + /* -ms-overflow-style: none; IE and Edge */ + /* scrollbar-width: 1; Firefox */ + scrollbar-face-color: #646464; + scrollbar-base-color: #646464; + scrollbar-3dlight-color: #646464; + scrollbar-highlight-color: #646464; + scrollbar-track-color: #000; + scrollbar-arrow-color: #000; + scrollbar-shadow-color: #646464; + scrollbar-dark-shadow-color: #646464; + + } + + ::-webkit-scrollbar { width: 8px; height: 3px;} + ::-webkit-scrollbar-button { background-color: #666; } + ::-webkit-scrollbar-track { background-color: #646464;} + ::-webkit-scrollbar-track-piece { background-color: #000;} + ::-webkit-scrollbar-thumb { height: 50px; background-color: #666; border-radius: 3px;} + ::-webkit-scrollbar-corner { background-color: #646464;} + ::-webkit-resizer { background-color: #666;} + } diff --git a/apps/embedded-catalog/react/src/hooks/queries/useProjectConnectors.tsx b/apps/embedded-catalog/react/src/hooks/queries/useProjectConnectors.tsx new file mode 100644 index 000000000..1949f8162 --- /dev/null +++ b/apps/embedded-catalog/react/src/hooks/queries/useProjectConnectors.tsx @@ -0,0 +1,16 @@ +import { useQuery } from '@tanstack/react-query'; +import config from '@/helpers/config'; + +const useProjectConnectors = (id: string) => { + return useQuery({ + queryKey: ['project-connectors', id], + queryFn: async (): Promise => { + 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(); + } + }); +}; +export default useProjectConnectors; diff --git a/apps/embedded-catalog/react/src/hooks/useOAuth.tsx b/apps/embedded-catalog/react/src/hooks/useOAuth.tsx index 5f7b64811..6209b069b 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 { constructAuthUrl } from '@panora/shared'; +import { ConnectorCategory, constructAuthUrl } from '@panora/shared'; type UseOAuthProps = { clientId?: string; providerName: string; // Name of the OAuth provider - vertical: string; // 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 diff --git a/apps/embedded-catalog/react/src/index.tsx b/apps/embedded-catalog/react/src/index.tsx index b80262a80..225dfd62c 100644 --- a/apps/embedded-catalog/react/src/index.tsx +++ b/apps/embedded-catalog/react/src/index.tsx @@ -1,96 +1,26 @@ import "./global.css"; -import useOAuth from '@/hooks/useOAuth'; -import { useEffect, useState } from 'react'; -import { getDescription, providersConfig } from '@panora/shared'; +import PanoraIntegrationCard, { ProviderCardProp } from "./components/PanoraIntegrationCard"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import PanoraDynamicCatalog, { DynamicCardProp } from "./components/PanoraDynamicCatalog"; -interface ProviderCardProp { - name: string; - vertical: string; - projectId: string; - returnUrl: string; - linkedUserId: string; - optionalApiUrl?: string, -} -const PanoraIntegrationCard = ({name, vertical, projectId, returnUrl, linkedUserId, optionalApiUrl}: ProviderCardProp) => { - const [providerClicked, setProviderClicked] = useState(false); - const [loading, setLoading] = useState(false) - - //const vertical = findProviderVertical(name.toLowerCase()) - //if(!projectId || !linkedUserId) return; - - const { open, isReady } = useOAuth({ - providerName: name.toLowerCase(), - vertical: vertical.toLowerCase(), - returnUrl: returnUrl, - projectId: projectId, - linkedUserId: linkedUserId, - optionalApiUrl: optionalApiUrl, - onSuccess: () => console.log('OAuth successful'), - }); - - const onWindowClose = () => { - setLoading(false); - return; - } - - useEffect(() => { - if (loading && providerClicked && isReady) { - open(onWindowClose); - return; - } - }, [providerClicked, isReady, open, loading]); - - - const handleClick = () => { - setLoading(true); - setProviderClicked(true); - return; - }; - - const img = providersConfig[vertical!.toLowerCase()][name.toLowerCase()].logoPath; - - return ( -
-
- -
Integrate with {name}
- -
- -

{getDescription(name.toLowerCase())}

- {!loading ? - - Connect in one click - - - : - <> -

Continue in {name}

-
- -
- - } -
- ) -}; - -const PanoraProviderCard = ({name, vertical, projectId, returnUrl, linkedUserId, optionalApiUrl}: ProviderCardProp) => { +const PanoraProviderCard = ({name, category, projectId, returnUrl, linkedUserId, optionalApiUrl}: ProviderCardProp) => { const queryClient = new QueryClient(); return ( - + ) } - -export default PanoraProviderCard; + +const PanoraDynamicCatalogCard = ({projectId, returnUrl, linkedUserId, category, optionalApiUrl} : DynamicCardProp) => { + const queryClient = new QueryClient(); + return ( + + + + ) + +} + +export {PanoraProviderCard,PanoraDynamicCatalogCard}; diff --git a/apps/embedded-catalog/react/src/lib/utils.ts b/apps/embedded-catalog/react/src/lib/utils.ts new file mode 100644 index 000000000..d084ccade --- /dev/null +++ b/apps/embedded-catalog/react/src/lib/utils.ts @@ -0,0 +1,6 @@ +import { type ClassValue, clsx } from "clsx" +import { twMerge } from "tailwind-merge" + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} diff --git a/apps/embedded-catalog/react/tailwind.config.js b/apps/embedded-catalog/react/tailwind.config.js index 2616547f4..9ede21ca0 100644 --- a/apps/embedded-catalog/react/tailwind.config.js +++ b/apps/embedded-catalog/react/tailwind.config.js @@ -4,6 +4,17 @@ module.exports = { theme: { extend: {}, }, + extend: { + keyframes: { + "shine": { + from: { backgroundPosition: '200% 0' }, + to: { backgroundPosition: '-200% 0' }, + }, + }, + animation: { + "shine": "shine 8s ease-in-out infinite", + }, + }, plugins: [], } - + diff --git a/apps/magic-link/.eslintrc.cjs b/apps/magic-link/.eslintrc.cjs index 5701e8c3d..bb5be0b31 100644 --- a/apps/magic-link/.eslintrc.cjs +++ b/apps/magic-link/.eslintrc.cjs @@ -15,5 +15,8 @@ module.exports = { { allowConstantExport: true }, ], '@typescript-eslint/no-unused-vars': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-non-null-asserted-optional-chain': 'off', + }, } diff --git a/apps/magic-link/components.json b/apps/magic-link/components.json new file mode 100644 index 000000000..0e2063558 --- /dev/null +++ b/apps/magic-link/components.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "default", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "tailwind.config.js", + "css": "src/index.css", + "baseColor": "slate", + "cssVariables": false, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils" + } +} \ No newline at end of file diff --git a/apps/magic-link/package.json b/apps/magic-link/package.json index c4374d74e..bfdf42ef6 100644 --- a/apps/magic-link/package.json +++ b/apps/magic-link/package.json @@ -9,22 +9,33 @@ "scripts": { "dev": "vite", "build": "tsc && vite build", - "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives", "preview": "vite preview", "ci": "pnpm run lint && pnpm run build" }, "dependencies": { "@panora/shared": "^1.2.2", "@panora/typescript-sdk": "^1.0.3", + "@radix-ui/react-label": "^2.0.2", + "@radix-ui/react-radio-group": "^1.1.3", + "@radix-ui/react-select": "^2.0.0", + "@radix-ui/react-separator": "^1.0.3", + "@radix-ui/react-slot": "^1.0.2", "@tanstack/react-query": "^5.12.2", "api": "workspace:*", + "class-variance-authority": "^0.7.0", + "clsx": "^2.1.0", + "lucide-react": "^0.344.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-loader-spinner": "^5.4.5", + "tailwind-merge": "^2.2.1", "tailwind-scrollbar-hide": "^1.1.7", + "tailwindcss-animate": "^1.0.7", "uuid": "^9.0.1" }, "devDependencies": { + "@types/node": "^20.12.12", "@types/react": "^18.2.15", "@types/react-dom": "^18.2.7", "@types/uuid": "^9.0.7", diff --git a/apps/magic-link/src/App.tsx b/apps/magic-link/src/App.tsx index bb4967ee6..e815e5f7a 100644 --- a/apps/magic-link/src/App.tsx +++ b/apps/magic-link/src/App.tsx @@ -1,11 +1,14 @@ import './App.css' import ProviderModal from './lib/ProviderModal' +import { ThemeProvider } from "@/components/theme-provider" function App() { return ( -
+ +
+
) } diff --git a/apps/magic-link/src/components/theme-provider.tsx b/apps/magic-link/src/components/theme-provider.tsx new file mode 100644 index 000000000..d89a82e11 --- /dev/null +++ b/apps/magic-link/src/components/theme-provider.tsx @@ -0,0 +1,73 @@ +import { createContext, useContext, useEffect, useState } from "react" + +type Theme = "dark" | "light" | "system" + +type ThemeProviderProps = { + children: React.ReactNode + defaultTheme?: Theme + storageKey?: string +} + +type ThemeProviderState = { + theme: Theme + setTheme: (theme: Theme) => void +} + +const initialState: ThemeProviderState = { + theme: "system", + setTheme: () => null, +} + +const ThemeProviderContext = createContext(initialState) + +export function ThemeProvider({ + children, + defaultTheme = "system", + storageKey = "vite-ui-theme", + ...props +}: ThemeProviderProps) { + const [theme, setTheme] = useState( + () => (localStorage.getItem(storageKey) as Theme) || defaultTheme + ) + + useEffect(() => { + const root = window.document.documentElement + + root.classList.remove("light", "dark") + + if (theme === "system") { + const systemTheme = window.matchMedia("(prefers-color-scheme: dark)") + .matches + ? "dark" + : "light" + + root.classList.add(systemTheme) + return + } + + root.classList.add(theme) + }, [theme]) + + const value = { + theme, + setTheme: (theme: Theme) => { + localStorage.setItem(storageKey, theme) + setTheme(theme) + }, + } + + return ( + + {children} + + ) +} + +export const useTheme = () => { + const context = useContext(ThemeProviderContext) + + if (context === undefined) + throw new Error("useTheme must be used within a ThemeProvider") + + return context +} diff --git a/apps/magic-link/src/components/ui/button.tsx b/apps/magic-link/src/components/ui/button.tsx new file mode 100644 index 000000000..0ba427735 --- /dev/null +++ b/apps/magic-link/src/components/ui/button.tsx @@ -0,0 +1,56 @@ +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const buttonVariants = cva( + "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground hover:bg-primary/90", + destructive: + "bg-destructive text-destructive-foreground hover:bg-destructive/90", + outline: + "border border-input bg-background hover:bg-accent hover:text-accent-foreground", + secondary: + "bg-secondary text-secondary-foreground hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-10 px-4 py-2", + sm: "h-9 rounded-md px-3", + lg: "h-11 rounded-md px-8", + icon: "h-10 w-10", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +) + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button" + return ( + + ) + } +) +Button.displayName = "Button" + +export { Button, buttonVariants } diff --git a/apps/magic-link/src/components/ui/button2.tsx b/apps/magic-link/src/components/ui/button2.tsx new file mode 100644 index 000000000..b1f45e2fc --- /dev/null +++ b/apps/magic-link/src/components/ui/button2.tsx @@ -0,0 +1,108 @@ +import * as React from "react"; +import { Slot, Slottable } from "@radix-ui/react-slot"; +import { cva, type VariantProps } from "class-variance-authority"; +import { cn } from "@/lib/utils"; + +const buttonVariants = cva( + "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground hover:bg-primary/90", + destructive: + "bg-destructive text-destructive-foreground hover:bg-destructive/90", + outline: + "border border-input bg-background hover:bg-accent hover:text-accent-foreground", + secondary: + "bg-secondary text-secondary-foreground hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", + expandIcon: + "group relative text-primary-foreground bg-primary hover:bg-primary/90", + ringHover: + "bg-primary text-primary-foreground transition-all duration-300 hover:bg-primary/90 hover:ring-2 hover:ring-primary/90 hover:ring-offset-2", + shine: + "text-primary-foreground animate-shine bg-gradient-to-r from-primary via-primary/75 to-primary bg-[length:400%_100%] ", + gooeyRight: + "text-primary-foreground relative bg-primary z-0 overflow-hidden transition-all duration-500 before:absolute before:inset-0 before:-z-10 before:translate-x-[150%] before:translate-y-[150%] before:scale-[2.5] before:rounded-[100%] before:bg-gradient-to-r from-zinc-400 before:transition-transform before:duration-1000 hover:before:translate-x-[0%] hover:before:translate-y-[0%] ", + gooeyLeft: + "text-primary-foreground relative bg-primary z-0 overflow-hidden transition-all duration-500 after:absolute after:inset-0 after:-z-10 after:translate-x-[-150%] after:translate-y-[150%] after:scale-[2.5] after:rounded-[100%] after:bg-gradient-to-l from-zinc-400 after:transition-transform after:duration-1000 hover:after:translate-x-[0%] hover:after:translate-y-[0%] ", + linkHover1: + "relative after:absolute after:bg-primary after:bottom-2 after:h-[1px] after:w-2/3 after:origin-bottom-left after:scale-x-100 hover:after:origin-bottom-right hover:after:scale-x-0 after:transition-transform after:ease-in-out after:duration-300", + linkHover2: + "relative after:absolute after:bg-primary after:bottom-2 after:h-[1px] after:w-2/3 after:origin-bottom-right after:scale-x-0 hover:after:origin-bottom-left hover:after:scale-x-100 after:transition-transform after:ease-in-out after:duration-300", + }, + size: { + default: "h-10 px-4 py-2", + sm: "h-9 rounded-md px-3", + lg: "h-11 rounded-md px-8", + icon: "h-10 w-10", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +); + +interface IconProps { + Icon: React.ElementType; + iconPlacement: "left" | "right"; +} + +interface IconRefProps { + Icon?: never; + iconPlacement?: undefined; +} + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean; +} + +export type ButtonIconProps = IconProps | IconRefProps; + +const Button = React.forwardRef< + HTMLButtonElement, + ButtonProps & ButtonIconProps +>( + ( + { + className, + variant, + size, + asChild = false, + Icon, + iconPlacement, + ...props + }, + ref + ) => { + const Comp = asChild ? Slot : "button"; + return ( + + {Icon && iconPlacement === "left" && ( +
+ +
+ )} + {props.children} + {Icon && iconPlacement === "right" && ( +
+ +
+ )} +
+ ); + } +); +Button.displayName = "Button"; + +export { Button, buttonVariants }; + \ No newline at end of file diff --git a/apps/magic-link/src/components/ui/card.tsx b/apps/magic-link/src/components/ui/card.tsx new file mode 100644 index 000000000..afa13ecfa --- /dev/null +++ b/apps/magic-link/src/components/ui/card.tsx @@ -0,0 +1,79 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +const Card = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +Card.displayName = "Card" + +const CardHeader = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardHeader.displayName = "CardHeader" + +const CardTitle = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)) +CardTitle.displayName = "CardTitle" + +const CardDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)) +CardDescription.displayName = "CardDescription" + +const CardContent = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)) +CardContent.displayName = "CardContent" + +const CardFooter = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardFooter.displayName = "CardFooter" + +export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } diff --git a/apps/magic-link/src/components/ui/label.tsx b/apps/magic-link/src/components/ui/label.tsx new file mode 100644 index 000000000..683faa793 --- /dev/null +++ b/apps/magic-link/src/components/ui/label.tsx @@ -0,0 +1,24 @@ +import * as React from "react" +import * as LabelPrimitive from "@radix-ui/react-label" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const labelVariants = cva( + "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" +) + +const Label = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & + VariantProps +>(({ className, ...props }, ref) => ( + +)) +Label.displayName = LabelPrimitive.Root.displayName + +export { Label } diff --git a/apps/magic-link/src/components/ui/loading-spinner.tsx b/apps/magic-link/src/components/ui/loading-spinner.tsx new file mode 100644 index 000000000..db4a2b4ac --- /dev/null +++ b/apps/magic-link/src/components/ui/loading-spinner.tsx @@ -0,0 +1,19 @@ +import { cn } from "@/lib/utils" + +export const LoadingSpinner = ({className} : {className?: string}) => { + return ( + + + ) +} \ No newline at end of file diff --git a/apps/magic-link/src/components/ui/radio-group.tsx b/apps/magic-link/src/components/ui/radio-group.tsx new file mode 100644 index 000000000..43b43b48b --- /dev/null +++ b/apps/magic-link/src/components/ui/radio-group.tsx @@ -0,0 +1,42 @@ +import * as React from "react" +import * as RadioGroupPrimitive from "@radix-ui/react-radio-group" +import { Circle } from "lucide-react" + +import { cn } from "@/lib/utils" + +const RadioGroup = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => { + return ( + + ) +}) +RadioGroup.displayName = RadioGroupPrimitive.Root.displayName + +const RadioGroupItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => { + return ( + + + + + + ) +}) +RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName + +export { RadioGroup, RadioGroupItem } diff --git a/apps/magic-link/src/components/ui/select.tsx b/apps/magic-link/src/components/ui/select.tsx new file mode 100644 index 000000000..fe56d4d3a --- /dev/null +++ b/apps/magic-link/src/components/ui/select.tsx @@ -0,0 +1,158 @@ +import * as React from "react" +import * as SelectPrimitive from "@radix-ui/react-select" +import { Check, ChevronDown, ChevronUp } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Select = SelectPrimitive.Root + +const SelectGroup = SelectPrimitive.Group + +const SelectValue = SelectPrimitive.Value + +const SelectTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + span]:line-clamp-1", + className + )} + {...props} + > + {children} + + + + +)) +SelectTrigger.displayName = SelectPrimitive.Trigger.displayName + +const SelectScrollUpButton = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)) +SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName + +const SelectScrollDownButton = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)) +SelectScrollDownButton.displayName = + SelectPrimitive.ScrollDownButton.displayName + +const SelectContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, position = "popper", ...props }, ref) => ( + + + + + {children} + + + + +)) +SelectContent.displayName = SelectPrimitive.Content.displayName + +const SelectLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SelectLabel.displayName = SelectPrimitive.Label.displayName + +const SelectItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + + {children} + +)) +SelectItem.displayName = SelectPrimitive.Item.displayName + +const SelectSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SelectSeparator.displayName = SelectPrimitive.Separator.displayName + +export { + Select, + SelectGroup, + SelectValue, + SelectTrigger, + SelectContent, + SelectLabel, + SelectItem, + SelectSeparator, + SelectScrollUpButton, + SelectScrollDownButton, +} diff --git a/apps/magic-link/src/components/ui/separator.tsx b/apps/magic-link/src/components/ui/separator.tsx new file mode 100644 index 000000000..75a84d15f --- /dev/null +++ b/apps/magic-link/src/components/ui/separator.tsx @@ -0,0 +1,29 @@ +import * as React from "react" +import * as SeparatorPrimitive from "@radix-ui/react-separator" + +import { cn } from "@/lib/utils" + +const Separator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>( + ( + { className, orientation = "horizontal", decorative = true, ...props }, + ref + ) => ( + + ) +) +Separator.displayName = SeparatorPrimitive.Root.displayName + +export { Separator } diff --git a/apps/magic-link/src/hooks/queries/useProjectConnectors.tsx b/apps/magic-link/src/hooks/queries/useProjectConnectors.tsx new file mode 100644 index 000000000..1949f8162 --- /dev/null +++ b/apps/magic-link/src/hooks/queries/useProjectConnectors.tsx @@ -0,0 +1,16 @@ +import { useQuery } from '@tanstack/react-query'; +import config from '@/helpers/config'; + +const useProjectConnectors = (id: string) => { + return useQuery({ + queryKey: ['project-connectors', id], + queryFn: async (): Promise => { + 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(); + } + }); +}; +export default useProjectConnectors; diff --git a/apps/magic-link/src/hooks/useOAuth.ts b/apps/magic-link/src/hooks/useOAuth.ts index 7b91206e6..ef590fc9c 100644 --- a/apps/magic-link/src/hooks/useOAuth.ts +++ b/apps/magic-link/src/hooks/useOAuth.ts @@ -22,29 +22,34 @@ const useOAuth = ({ providerName, vertical, returnUrl, projectId, linkedUserId, const openModal = async (onWindowClose: () => void) => { - 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(() => { - if (authWindow!.closed) { - clearInterval(interval); - if (onWindowClose) { - onWindowClose(); - } - onSuccess(); + 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); } - }, 500); + 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(() => { + if (authWindow!.closed) { + clearInterval(interval); + if (onWindowClose) { + onWindowClose(); + } + onSuccess(); + } + }, 500); - return authWindow; + return authWindow; + } catch (error) { + console.error('Failed to open OAuth window', error); + onWindowClose(); // Reset the loading state + } }; return { open: openModal, isReady }; diff --git a/apps/magic-link/src/index.css b/apps/magic-link/src/index.css index f9b02a22c..5161271e3 100644 --- a/apps/magic-link/src/index.css +++ b/apps/magic-link/src/index.css @@ -2,117 +2,58 @@ @tailwind components; @tailwind utilities; -:root { - font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; - - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: seal; - - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - -webkit-text-size-adjust: 100%; -} - -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; -} -a:hover { - color: #535bf2; -} - -body { - margin: 0; - display: flex; - place-items: center; - min-width: 320px; - min-height: 100vh; - font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; -} - -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; -} -button:hover { - border-color: #efedea; -} - -@media (prefers-color-scheme: light) { +@layer base { :root { - color: #213547; - background-color: #ffffff; - } - a:hover { - color: #747bff; - } - button { - background-color: #b0e0e6; + --background: 0 0% 100%; + --foreground: 240 10% 3.9%; + --card: 0 0% 100%; + --card-foreground: 240 10% 3.9%; + --popover: 0 0% 100%; + --popover-foreground: 240 10% 3.9%; + --primary: 240 5.9% 10%; + --primary-foreground: 0 0% 98%; + --secondary: 240 4.8% 95.9%; + --secondary-foreground: 240 5.9% 10%; + --muted: 240 4.8% 95.9%; + --muted-foreground: 240 3.8% 46.1%; + --accent: 240 4.8% 95.9%; + --accent-foreground: 240 5.9% 10%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 0 0% 98%; + --border: 240 5.9% 90%; + --input: 240 5.9% 90%; + --ring: 240 5.9% 10%; + --radius: 0.5rem; } -} - -.modal { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-color: rgba(0, 0, 0, 0.5); - display: flex; - justify-content: center; - align-items: center; -} -.modal-content { - background-color: white; - padding: 20px; - border-radius: 10px; - text-align: center; -} - -.modal-content button { - margin: 10px; - padding: 10px 20px; -} -/* CSS */ -.loading-animation-wrapper { - position: relative; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; -} - -.icon-border { - position: absolute; - width: 100px; /* Match this to the size of your icon */ - height: 100px; /* Match this to the size of your icon */ -} - -.icon { - width: 100px; /* Match this to the size of your icon */ - height: 100px; /* Match this to the size of your icon */ - border-radius: 0.75rem; /* Tailwind's rounded-xl */ - position: relative; - z-index: 1; + .dark { + --background: 240 10% 3.9%; + --foreground: 0 0% 98%; + --card: 240 10% 3.9%; + --card-foreground: 0 0% 98%; + --popover: 240 10% 3.9%; + --popover-foreground: 0 0% 98%; + --primary: 0 0% 98%; + --primary-foreground: 240 5.9% 10%; + --secondary: 240 3.7% 15.9%; + --secondary-foreground: 0 0% 98%; + --muted: 240 3.7% 15.9%; + --muted-foreground: 240 5% 64.9%; + --accent: 240 3.7% 15.9%; + --accent-foreground: 0 0% 98%; + --destructive: 0 62.8% 46.8%; + --destructive-foreground: 0 0% 98%; + --border: 240 3.7% 15.9%; + --input: 240 3.7% 15.9%; + --ring: 240 4.9% 83.9%; + } } -.text { - position: absolute; - top: 120px; /* Position below the icon */ - text-align: center; - color: white; +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + } } diff --git a/apps/magic-link/src/lib/ProviderModal.tsx b/apps/magic-link/src/lib/ProviderModal.tsx index 8a92aab2e..e44e743ef 100644 --- a/apps/magic-link/src/lib/ProviderModal.tsx +++ b/apps/magic-link/src/lib/ProviderModal.tsx @@ -1,9 +1,16 @@ import { useEffect, useState } from 'react'; import useOAuth from '@/hooks/useOAuth'; -import { categoriesVerticals } from '@panora/shared/src/providers'; -import {findProviderByName, providersArray} from '@panora/shared/src/utils'; +import { findProviderByName, providersArray, categoryFromSlug, Provider } from '@panora/shared/src'; +import { categoriesVerticals } from '@panora/shared/src/categories'; import useLinkedUser from '@/hooks/queries/useLinkedUser'; import useUniqueMagicLink from '@/hooks/queries/useUniqueMagicLink'; +import { Card, CardContent, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'; +import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'; +import { Label } from '@/components/ui/label'; +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'; const LoadingOverlay = ({ providerName }: { providerName: string }) => { @@ -26,13 +33,27 @@ const LoadingOverlay = ({ providerName }: { providerName: string }) => { }; const ProviderModal = () => { - const [selectedCategory, setSelectedCategory] = useState(categoriesVerticals[0] as string); // Default to the first category - const [selectedProvider, setSelectedProvider] = useState(''); + const [selectedCategory, setSelectedCategory] = useState("All"); + const [selectedProvider, setSelectedProvider] = useState<{ + provider: string; + category: string; + }>(); + + const [startFlow, setStartFlow] = useState(false); + const [preStartFlow, setPreStartFlow] = useState(false); + + const [data, setData] = useState([]); + const [loading, setLoading] = useState<{ status: boolean; provider: string }>({status: false, provider: ''}); + const [uniqueMagicLinkId, setUniqueMagicLinkId] = useState(''); + const {data: magicLink} = useUniqueMagicLink(uniqueMagicLinkId); + const {data: linkedUser} = useLinkedUser(magicLink?.id_linked_user as string); + const {data: connectorsForProject} = useProjectConnectors(linkedUser?.id_project as string); + useEffect(() => { const queryParams = new URLSearchParams(window.location.search); const uniqueId = queryParams.get('uniqueLink'); @@ -41,91 +62,148 @@ const ProviderModal = () => { } }, []); - - const {data: magicLink} = useUniqueMagicLink(uniqueMagicLinkId); - const {data: linkedUser} = useLinkedUser(magicLink?.id_linked_user as string); + 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()) + } + }, [connectorsForProject, selectedCategory]) + const { open, isReady } = useOAuth({ - providerName: selectedProvider, - vertical: selectedCategory, - returnUrl: "https://google.com", //TODO: handle the redirection URL (let customer put their confetti or success page redirect url ? ) + providerName: selectedProvider?.provider!, + vertical: selectedProvider?.category!, + returnUrl: "https://google.com", projectId: linkedUser?.id_project as string, linkedUserId: linkedUser?.id_linked_user as string, onSuccess: () => console.log('OAuth successful'), }); const onWindowClose = () => { - setSelectedProvider(''); + setSelectedProvider({ + provider: '', + category: '' + }); setLoading({ status: false, provider: '' }) + setStartFlow(false); + setPreStartFlow(false); } useEffect(() => { - if (selectedProvider && isReady) { - open(onWindowClose); + if (startFlow && isReady) { + open(onWindowClose); + } else if (startFlow && !isReady) { + setLoading({ + status: false, + provider: '' + }); } - - }, [selectedProvider, isReady, open]); + }, [startFlow, isReady, open]); + - const handleWalletClick = (walletName: string) => { - console.log(`Wallet selected: ${walletName}`); - setSelectedProvider(walletName.toLowerCase()); - setLoading({status: true, provider: walletName}); + + const handleWalletClick = (walletName: string, category: string) => { + setSelectedProvider({provider: walletName.toLowerCase(), category: category.toLowerCase()}); + setPreStartFlow(true); }; - const handleCategoryClick = (category: string) => { + const handleStartFlow = () => { + setLoading({status: true, provider: selectedProvider?.provider!}); + setStartFlow(true); + } + + const handleCategoryClick = (category: string) => { + setPreStartFlow(false); + setSelectedProvider({ + provider: '', + category: '' + }); setSelectedCategory(category); }; - - const PROVIDERS = providersArray(selectedCategory); + + function transformConnectorsStatus(connectors : {[key: string]: boolean}): { connector_name: string;category: string; status: string }[] { + return Object.entries(connectors).flatMap(([key, value]) => { + const [category_slug, connector_name] = key.split('_').map((part: string) => part.trim()); + const category = categoryFromSlug(category_slug); + if (category != null) { + return [{ + connector_name: connector_name, + category: category, + status: String(value) + }]; + } + return []; + }); + } return ( -
-
- {!loading.status &&
-

Select Integrations

- -
} - {!loading.status ? -
-
- {categoriesVerticals.map((category, index) => ( - - ))} -
- {( - <> - {PROVIDERS.map((provider, index) => ( -
handleWalletClick(provider.name)} - > -
- {provider.name} - {provider.name.substring(0,1).toUpperCase().concat(provider.name.substring(1,provider.name.length))} -
- -
- ))} - - - )} -
- : - - } -
-
+ + + Connect to your software + + + +
+ + {(data as Provider[]).map((provider) => ( +
+ handleWalletClick(provider.name, provider.vertical!)} + /> + +
+ ))} +
+
+
+ + {loading.status ? : } + +
); }; diff --git a/apps/magic-link/src/lib/utils.ts b/apps/magic-link/src/lib/utils.ts new file mode 100644 index 000000000..d084ccade --- /dev/null +++ b/apps/magic-link/src/lib/utils.ts @@ -0,0 +1,6 @@ +import { type ClassValue, clsx } from "clsx" +import { twMerge } from "tailwind-merge" + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} diff --git a/apps/magic-link/tailwind.config.js b/apps/magic-link/tailwind.config.js index 224857175..aa05ed0b5 100644 --- a/apps/magic-link/tailwind.config.js +++ b/apps/magic-link/tailwind.config.js @@ -1,14 +1,88 @@ /** @type {import('tailwindcss').Config} */ -export default { +module.exports = { + darkMode: ["class"], content: [ - "./index.html", - "./src/**/*.{js,ts,jsx,tsx}", + './pages/**/*.{ts,tsx}', + './components/**/*.{ts,tsx}', + './app/**/*.{ts,tsx}', + './src/**/*.{ts,tsx}', ], + prefix: "", theme: { - extend: {}, + container: { + center: true, + padding: "2rem", + screens: { + "2xl": "1400px", + }, + }, + extend: { + colors: { + border: "hsl(var(--border))", + input: "hsl(var(--input))", + ring: "hsl(var(--ring))", + background: "hsl(var(--background))", + foreground: "hsl(var(--foreground))", + primary: { + DEFAULT: "hsl(var(--primary))", + foreground: "hsl(var(--primary-foreground))", + }, + secondary: { + DEFAULT: "hsl(var(--secondary))", + foreground: "hsl(var(--secondary-foreground))", + }, + destructive: { + DEFAULT: "hsl(var(--destructive))", + foreground: "hsl(var(--destructive-foreground))", + }, + muted: { + DEFAULT: "hsl(var(--muted))", + foreground: "hsl(var(--muted-foreground))", + }, + accent: { + DEFAULT: "hsl(var(--accent))", + foreground: "hsl(var(--accent-foreground))", + }, + popover: { + DEFAULT: "hsl(var(--popover))", + foreground: "hsl(var(--popover-foreground))", + }, + card: { + DEFAULT: "hsl(var(--card))", + foreground: "hsl(var(--card-foreground))", + }, + }, + borderRadius: { + lg: "var(--radius)", + md: "calc(var(--radius) - 2px)", + sm: "calc(var(--radius) - 4px)", + }, + keyframes: { + "accordion-down": { + from: { height: "0" }, + to: { height: "var(--radix-accordion-content-height)" }, + }, + "accordion-up": { + from: { height: "var(--radix-accordion-content-height)" }, + to: { height: "0" }, + }, + }, + animation: { + "accordion-down": "accordion-down 0.2s ease-out", + "accordion-up": "accordion-up 0.2s ease-out", + }, + }, }, - plugins: [ - require('tailwind-scrollbar-hide') - ], -} - + extend: { + keyframes: { + "shine": { + from: { backgroundPosition: '200% 0' }, + to: { backgroundPosition: '-200% 0' }, + }, + }, + animation: { + "shine": "shine 8s ease-in-out infinite", + }, +}, + plugins: [require("tailwindcss-animate")], +} \ No newline at end of file diff --git a/apps/magic-link/vite.config.ts b/apps/magic-link/vite.config.ts index d9d156257..5790e8202 100644 --- a/apps/magic-link/vite.config.ts +++ b/apps/magic-link/vite.config.ts @@ -1,13 +1,12 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' -import path from 'path' +import path from "path" +import react from "@vitejs/plugin-react" +import { defineConfig } from "vite" -// https://vitejs.dev/config/ export default defineConfig({ plugins: [react()], resolve: { alias: { - '@': path.resolve(__dirname, './src') - } - } + "@": path.resolve(__dirname, "./src"), + }, + }, }) diff --git a/docs/open-source/contributors.mdx b/docs/open-source/contributors.mdx index 81b71ed71..4766d8826 100644 --- a/docs/open-source/contributors.mdx +++ b/docs/open-source/contributors.mdx @@ -81,7 +81,7 @@ First choose wisely which vertical the 3rd party belongs to among these: # Step 1: Ensure 3rd party metadata is set -Look into the `packages/shared/src/utils.ts` file and check if the provider you want to build has its metadata set inside the `providersConfig` object. +Look into the `packages/shared/src/utils.ts` file and check if the provider you want to build has its metadata set inside the `CONNECTORS_METADATA` object. It should be available (if not [contact us](https://app.cal.com/rflih/30)) with `active` field set to `false` meaning the integration has not been built. @@ -101,7 +101,7 @@ For the sake of this guide, let's map the common object `contact` under `crm` ve **An integration is considered valid when all common objects have been mapped. Then, after the PR is accepted we'll be able to set `active` field to `true` - inside `providersConfig`** + inside `CONNECTORS_METADATA`** diff --git a/packages/api/prisma/schema.prisma b/packages/api/prisma/schema.prisma index 36d9d8311..ded59f6b5 100644 --- a/packages/api/prisma/schema.prisma +++ b/packages/api/prisma/schema.prisma @@ -7,61 +7,6 @@ datasource db { url = env("DATABASE_URL") } -/// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments -model entity { - id_entity String @id(map: "pk_entity") @db.Uuid - ressource_owner_id String @db.Uuid - attribute attribute[] - value value[] -} - -/// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments -model remote_data { - id_remote_data String @id(map: "pk_remote_data") @db.Uuid - ressource_owner_id String? @unique(map: "force_unique_ressourceownerid") @db.Uuid - format String? - data String? - created_at DateTime? @db.Timestamp(6) -} - -/// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments -model tcg_accounts { - id_tcg_account String @id(map: "pk_tcg_account") @db.Uuid - remote_id String? - name String? - domains String[] - remote_platform String? - created_at DateTime @db.Timestamp(6) - modified_at DateTime @db.Timestamp(6) - id_linked_user String? @db.Uuid - tcg_contacts tcg_contacts[] -} - -model tcg_teams { - id_tcg_team String @id(map: "pk_tcg_teams") @db.Uuid - remote_id String? - remote_platform String? - name String? - description String? - created_at DateTime @db.Timestamp(6) - modified_at DateTime @db.Timestamp(6) - id_linked_user String? @db.Uuid -} - -/// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments -model tcg_users { - id_tcg_user String @id(map: "pk_tcg_users") @db.Uuid - name String? - email_address String? - remote_id String? - remote_platform String? - teams String[] - created_at DateTime? @db.Timestamp(6) - modified_at DateTime? @db.Timestamp(6) - id_linked_user String? @db.Uuid - tcg_comments tcg_comments[] -} - /// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments model users { id_user String @id(map: "pk_users") @db.Uuid @@ -105,27 +50,6 @@ model webhooks_reponses { webhook_delivery_attempts webhook_delivery_attempts[] } -/// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments -model connection_strategies { - id_connection_strategy String @id(map: "pk_connection_strategies") @db.Uuid - status Boolean - type String - id_project String? @db.Uuid -} - -model tcg_collections { - id_tcg_collection String @id(map: "pk_tcg_collections") @db.Uuid - name String? - description String? - remote_id String? - remote_platform String? - collection_type String? - parent_collection String? @db.Uuid - created_at DateTime @db.Timestamp(6) - modified_at DateTime @db.Timestamp(6) - id_linked_user String @db.Uuid -} - model api_keys { id_api_key String @id(map: "id_") @db.Uuid api_key_hash String @unique(map: "unique_api_keys") @@ -159,6 +83,14 @@ model attribute { @@index([id_entity], map: "fk_attribute_entityid") } +/// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments +model connection_strategies { + id_connection_strategy String @id(map: "pk_connection_strategies") @db.Uuid + status Boolean + type String + id_project String? @db.Uuid +} + /// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments model connections { id_connection String @id(map: "pk_connections") @db.Uuid @@ -431,6 +363,14 @@ model cs_values { id_cs_attribute String @db.Uuid } +/// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments +model entity { + id_entity String @id(map: "pk_entity") @db.Uuid + ressource_owner_id String @db.Uuid + attribute attribute[] + value value[] +} + /// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments model events { id_event String @id(map: "pk_jobs") @db.Uuid @@ -485,18 +425,57 @@ model linked_users { @@index([id_project], map: "fk_proectid_linked_users") } +model project_connectors { + id_project_connector String @id(map: "pk_project_connectors") @db.Uuid + id_project String @db.Uuid + crm_hubspot Boolean + crm_zoho Boolean + crm_zendesk Boolean + crm_pipedrive Boolean + crm_attio Boolean + tcg_zendesk Boolean + tcg_gorgias Boolean + tcg_front Boolean + tcg_jira Boolean + tcg_gitlab Boolean + projects projects @relation(fields: [id_project], references: [id_project], onDelete: NoAction, onUpdate: NoAction, map: "fk_project_connectors") +} + /// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments model projects { - id_project String @id(map: "pk_projects") @db.Uuid - name String - sync_mode String - pull_frequency BigInt? - redirect_url String? - id_user String @db.Uuid - api_keys api_keys[] - connections connections[] - linked_users linked_users[] - users users @relation(fields: [id_user], references: [id_user], onDelete: NoAction, onUpdate: NoAction, map: "fk_46_1") + id_project String @id(map: "pk_projects") @db.Uuid + name String + sync_mode String + pull_frequency BigInt? + redirect_url String? + id_user String @db.Uuid + api_keys api_keys[] + connections connections[] + linked_users linked_users[] + project_connectors project_connectors[] + users users @relation(fields: [id_user], references: [id_user], onDelete: NoAction, onUpdate: NoAction, map: "fk_46_1") +} + +/// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments +model remote_data { + id_remote_data String @id(map: "pk_remote_data") @db.Uuid + ressource_owner_id String? @unique(map: "force_unique_ressourceownerid") @db.Uuid + format String? + data String? + created_at DateTime? @db.Timestamp(6) +} + +/// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments +model tcg_accounts { + id_tcg_account String @id(map: "pk_tcg_account") @db.Uuid + remote_id String? + name String? + domains String[] + remote_platform String? + created_at DateTime @db.Timestamp(6) + modified_at DateTime @db.Timestamp(6) + id_linked_user String? @db.Uuid + tcg_contacts tcg_contacts[] } /// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments @@ -519,6 +498,19 @@ model tcg_attachments { @@index([id_tcg_ticket], map: "fk_tcg_attachment_tcg_ticketid") } +model tcg_collections { + id_tcg_collection String @id(map: "pk_tcg_collections") @db.Uuid + name String? + description String? + remote_id String? + remote_platform String? + collection_type String? + parent_collection String? @db.Uuid + created_at DateTime @db.Timestamp(6) + modified_at DateTime @db.Timestamp(6) + id_linked_user String @db.Uuid +} + /// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments model tcg_comments { id_tcg_comment String @id(map: "pk_tcg_comments") @db.Uuid @@ -577,6 +569,17 @@ model tcg_tags { @@index([id_tcg_ticket], map: "fk_tcg_tag_tcg_ticketid") } +model tcg_teams { + id_tcg_team String @id(map: "pk_tcg_teams") @db.Uuid + remote_id String? + remote_platform String? + name String? + description String? + created_at DateTime @db.Timestamp(6) + modified_at DateTime @db.Timestamp(6) + id_linked_user String? @db.Uuid +} + /// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments model tcg_tickets { id_tcg_ticket String @id(map: "pk_tcg_tickets") @db.Uuid @@ -605,6 +608,20 @@ model tcg_tickets { @@index([id_tcg_user], map: "fk_tcg_ticket_tcg_user") } +/// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments +model tcg_users { + id_tcg_user String @id(map: "pk_tcg_users") @db.Uuid + name String? + email_address String? + remote_id String? + remote_platform String? + teams String[] + created_at DateTime? @db.Timestamp(6) + modified_at DateTime? @db.Timestamp(6) + id_linked_user String? @db.Uuid + tcg_comments tcg_comments[] +} + /// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments model value { id_value String @id(map: "pk_value") @db.Uuid diff --git a/packages/api/scripts/init.sql b/packages/api/scripts/init.sql index 8f5d2e92c..3ea0bac32 100644 --- a/packages/api/scripts/init.sql +++ b/packages/api/scripts/init.sql @@ -1,4 +1,6 @@ + + -- ************************************** webhooks_reponses CREATE TABLE webhooks_reponses @@ -91,7 +93,6 @@ COMMENT ON CONSTRAINT force_stytch_id_unique ON users IS 'force unique on stytch - -- ************************************** tcg_users CREATE TABLE tcg_users @@ -436,7 +437,25 @@ COMMENT ON COLUMN projects.pull_frequency IS 'frequency in seconds for pulls ex 3600 for one hour'; +-- ************************************** project_connectors +CREATE TABLE project_connectors +( + id_project_connector uuid NOT NULL, + id_project uuid NOT NULL, + crm_hubspot boolean NOT NULL, + crm_zoho boolean NOT NULL, + crm_zendesk boolean NOT NULL, + crm_pipedrive boolean NOT NULL, + crm_attio boolean NOT NULL, + tcg_zendesk boolean NOT NULL, + tcg_gorgias boolean NOT NULL, + tcg_front boolean NOT NULL, + tcg_jira boolean NOT NULL, + tcg_gitlab boolean NOT NULL, + CONSTRAINT PK_project_connectors PRIMARY KEY ( id_project_connector ), + CONSTRAINT FK_project_connectors FOREIGN KEY ( id_project ) REFERENCES projects ( id_project ) +); -- ************************************** crm_contacts diff --git a/packages/api/src/@core/connections-strategies/connections-strategies.service.ts b/packages/api/src/@core/connections-strategies/connections-strategies.service.ts index ab65afb88..c31ab961b 100644 --- a/packages/api/src/@core/connections-strategies/connections-strategies.service.ts +++ b/packages/api/src/@core/connections-strategies/connections-strategies.service.ts @@ -9,7 +9,7 @@ import { extractProvider, extractVertical, needsSubdomain, - providersConfig, + CONNECTORS_METADATA, } from '@panora/shared'; import { SoftwareMode } from '@panora/shared'; import { v4 as uuidv4 } from 'uuid'; @@ -224,13 +224,13 @@ export class ConnectionsStrategiesService { ), }; const scopes = - providersConfig[vertical.toLowerCase()][provider.toLowerCase()] + CONNECTORS_METADATA[vertical.toLowerCase()][provider.toLowerCase()] .scopes; if (scopes) { data = { ...data, SCOPE: - providersConfig[vertical.toLowerCase()][provider.toLowerCase()] + CONNECTORS_METADATA[vertical.toLowerCase()][provider.toLowerCase()] .scopes, }; } diff --git a/packages/api/src/@core/connections/accounting/services/freeagent/freeagent.service.ts b/packages/api/src/@core/connections/accounting/services/freeagent/freeagent.service.ts index fc411d48a..74e2db9d3 100644 --- a/packages/api/src/@core/connections/accounting/services/freeagent/freeagent.service.ts +++ b/packages/api/src/@core/connections/accounting/services/freeagent/freeagent.service.ts @@ -12,7 +12,7 @@ import { IAccountingConnectionService, } from '../../types'; import { ServiceRegistry } from '../registry.service'; -import { AuthStrategy, providersConfig } from '@panora/shared'; +import { AuthStrategy, CONNECTORS_METADATA } from '@panora/shared'; import { OAuth2AuthData, providerToType } from '@panora/shared'; import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service'; @@ -95,7 +95,7 @@ export class FreeagentConnectionService access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), account_url: - providersConfig['accounting']['freshagent'].urls.apiUrl, + CONNECTORS_METADATA['accounting']['freshagent'].urls.apiUrl, expiration_timestamp: new Date( new Date().getTime() + Number(data.expires_in) * 1000, ), @@ -112,7 +112,7 @@ export class FreeagentConnectionService vertical: 'accounting', token_type: 'oauth', account_url: - providersConfig['accounting']['freshagent'].urls.apiUrl, + CONNECTORS_METADATA['accounting']['freshagent'].urls.apiUrl, access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), expiration_timestamp: new Date( diff --git a/packages/api/src/@core/connections/accounting/services/freshbooks/freshbooks.service.ts b/packages/api/src/@core/connections/accounting/services/freshbooks/freshbooks.service.ts index 83643deb9..c97427812 100644 --- a/packages/api/src/@core/connections/accounting/services/freshbooks/freshbooks.service.ts +++ b/packages/api/src/@core/connections/accounting/services/freshbooks/freshbooks.service.ts @@ -12,7 +12,7 @@ import { IAccountingConnectionService, } from '../../types'; import { ServiceRegistry } from '../registry.service'; -import { AuthStrategy, providersConfig } from '@panora/shared'; +import { AuthStrategy, CONNECTORS_METADATA } from '@panora/shared'; import { OAuth2AuthData, providerToType } from '@panora/shared'; import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service'; @@ -94,7 +94,7 @@ export class FreshbooksConnectionService access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), account_url: - providersConfig['accounting']['freshbooks'].urls.apiUrl, + CONNECTORS_METADATA['accounting']['freshbooks'].urls.apiUrl, expiration_timestamp: new Date( new Date().getTime() + Number(data.expires_in) * 1000, ), @@ -111,7 +111,7 @@ export class FreshbooksConnectionService vertical: 'accounting', token_type: 'oauth', account_url: - providersConfig['accounting']['freshbooks'].urls.apiUrl, + CONNECTORS_METADATA['accounting']['freshbooks'].urls.apiUrl, access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), expiration_timestamp: new Date( diff --git a/packages/api/src/@core/connections/accounting/services/moneybird/moneybird.service.ts b/packages/api/src/@core/connections/accounting/services/moneybird/moneybird.service.ts index 4d2085d3a..e95355793 100644 --- a/packages/api/src/@core/connections/accounting/services/moneybird/moneybird.service.ts +++ b/packages/api/src/@core/connections/accounting/services/moneybird/moneybird.service.ts @@ -12,7 +12,7 @@ import { IAccountingConnectionService, } from '../../types'; import { ServiceRegistry } from '../registry.service'; -import { AuthStrategy, providersConfig } from '@panora/shared'; +import { AuthStrategy, CONNECTORS_METADATA } from '@panora/shared'; import { OAuth2AuthData, providerToType } from '@panora/shared'; import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service'; @@ -94,7 +94,7 @@ export class MoneybirdConnectionService data: { access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), - account_url: providersConfig['accounting']['moneybird'].urls.apiUrl, + account_url: CONNECTORS_METADATA['accounting']['moneybird'].urls.apiUrl, expiration_timestamp: new Date( new Date().getTime() + Number(data.expires_in) * 1000, ), @@ -110,7 +110,7 @@ export class MoneybirdConnectionService provider_slug: 'moneybird', vertical: 'accounting', token_type: 'oauth', - account_url: providersConfig['accounting']['moneybird'].urls.apiUrl, + account_url: CONNECTORS_METADATA['accounting']['moneybird'].urls.apiUrl, access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), expiration_timestamp: new Date( diff --git a/packages/api/src/@core/connections/accounting/services/pennylane/pennylane.service.ts b/packages/api/src/@core/connections/accounting/services/pennylane/pennylane.service.ts index bacf07abc..8ca4ba266 100644 --- a/packages/api/src/@core/connections/accounting/services/pennylane/pennylane.service.ts +++ b/packages/api/src/@core/connections/accounting/services/pennylane/pennylane.service.ts @@ -12,7 +12,7 @@ import { IAccountingConnectionService, } from '../../types'; import { ServiceRegistry } from '../registry.service'; -import { AuthStrategy, providersConfig } from '@panora/shared'; +import { AuthStrategy, CONNECTORS_METADATA } from '@panora/shared'; import { OAuth2AuthData, providerToType } from '@panora/shared'; import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service'; @@ -92,7 +92,7 @@ export class PennylaneConnectionService data: { access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), - account_url: providersConfig['accounting']['pennylane'].urls.apiUrl, + account_url: CONNECTORS_METADATA['accounting']['pennylane'].urls.apiUrl, expiration_timestamp: new Date( new Date().getTime() + Number(data.expires_in) * 1000, ), @@ -108,7 +108,7 @@ export class PennylaneConnectionService provider_slug: 'pennylane', vertical: 'accounting', token_type: 'oauth', - account_url: providersConfig['accounting']['pennylane'].urls.apiUrl, + account_url: CONNECTORS_METADATA['accounting']['pennylane'].urls.apiUrl, access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), expiration_timestamp: new Date( diff --git a/packages/api/src/@core/connections/accounting/services/quickbooks/quickbooks.service.ts b/packages/api/src/@core/connections/accounting/services/quickbooks/quickbooks.service.ts index dffa22876..07c214cac 100644 --- a/packages/api/src/@core/connections/accounting/services/quickbooks/quickbooks.service.ts +++ b/packages/api/src/@core/connections/accounting/services/quickbooks/quickbooks.service.ts @@ -12,7 +12,7 @@ import { IAccountingConnectionService, } from '../../types'; import { ServiceRegistry } from '../registry.service'; -import { AuthStrategy, providersConfig } from '@panora/shared'; +import { AuthStrategy, CONNECTORS_METADATA } from '@panora/shared'; import { OAuth2AuthData, providerToType } from '@panora/shared'; import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service'; @@ -95,7 +95,7 @@ export class QuickbooksConnectionService access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), account_url: - providersConfig['accounting']['quickbooks'].urls.apiUrl, + CONNECTORS_METADATA['accounting']['quickbooks'].urls.apiUrl, expiration_timestamp: new Date( new Date().getTime() + Number(data.expires_in) * 1000, ), @@ -112,7 +112,7 @@ export class QuickbooksConnectionService vertical: 'accounting', token_type: 'oauth', account_url: - providersConfig['accounting']['quickbooks'].urls.apiUrl, + CONNECTORS_METADATA['accounting']['quickbooks'].urls.apiUrl, access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), expiration_timestamp: new Date( diff --git a/packages/api/src/@core/connections/accounting/services/sage/sage.service.ts b/packages/api/src/@core/connections/accounting/services/sage/sage.service.ts index f29cb1b03..d2c6b1f3b 100644 --- a/packages/api/src/@core/connections/accounting/services/sage/sage.service.ts +++ b/packages/api/src/@core/connections/accounting/services/sage/sage.service.ts @@ -12,7 +12,7 @@ import { IAccountingConnectionService, } from '../../types'; import { ServiceRegistry } from '../registry.service'; -import { AuthStrategy, providersConfig } from '@panora/shared'; +import { AuthStrategy, CONNECTORS_METADATA } from '@panora/shared'; import { OAuth2AuthData, providerToType } from '@panora/shared'; import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service'; @@ -93,7 +93,7 @@ export class SageConnectionService implements IAccountingConnectionService { data: { access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), - account_url: providersConfig['accounting']['sage'].urls.apiUrl, + account_url: CONNECTORS_METADATA['accounting']['sage'].urls.apiUrl, expiration_timestamp: new Date( new Date().getTime() + Number(data.expires_in) * 1000, ), @@ -109,7 +109,7 @@ export class SageConnectionService implements IAccountingConnectionService { provider_slug: 'sage', vertical: 'accounting', token_type: 'oauth', - account_url: providersConfig['accounting']['sage'].urls.apiUrl, + account_url: CONNECTORS_METADATA['accounting']['sage'].urls.apiUrl, access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), expiration_timestamp: new Date( diff --git a/packages/api/src/@core/connections/accounting/services/wave_financial/wave_financial.service.ts b/packages/api/src/@core/connections/accounting/services/wave_financial/wave_financial.service.ts index 08c531017..c3b83024d 100644 --- a/packages/api/src/@core/connections/accounting/services/wave_financial/wave_financial.service.ts +++ b/packages/api/src/@core/connections/accounting/services/wave_financial/wave_financial.service.ts @@ -12,7 +12,7 @@ import { IAccountingConnectionService, } from '../../types'; import { ServiceRegistry } from '../registry.service'; -import { AuthStrategy, providersConfig } from '@panora/shared'; +import { AuthStrategy, CONNECTORS_METADATA } from '@panora/shared'; import { OAuth2AuthData, providerToType } from '@panora/shared'; import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service'; @@ -100,7 +100,7 @@ export class WaveFinancialConnectionService access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), account_url: - providersConfig['accounting']['wave_financial'].urls.apiUrl, + CONNECTORS_METADATA['accounting']['wave_financial'].urls.apiUrl, expiration_timestamp: new Date( new Date().getTime() + Number(data.expires_in) * 1000, ), @@ -117,7 +117,7 @@ export class WaveFinancialConnectionService vertical: 'accounting', token_type: 'oauth', account_url: - providersConfig['accounting']['wave_financial'].urls.apiUrl, + CONNECTORS_METADATA['accounting']['wave_financial'].urls.apiUrl, access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), expiration_timestamp: new Date( diff --git a/packages/api/src/@core/connections/connections.controller.ts b/packages/api/src/@core/connections/connections.controller.ts index ddd923cb8..c1158b4ee 100644 --- a/packages/api/src/@core/connections/connections.controller.ts +++ b/packages/api/src/@core/connections/connections.controller.ts @@ -13,7 +13,7 @@ import { NotFoundError, handleServiceError } from '@@core/utils/errors'; import { PrismaService } from '@@core/prisma/prisma.service'; import { ApiOperation, ApiQuery, ApiResponse, ApiTags } from '@nestjs/swagger'; import { TicketingConnectionsService } from './ticketing/services/ticketing.connection.service'; -import { ProviderVertical } from '@panora/shared'; +import { ConnectorCategory } from '@panora/shared'; import { AccountingConnectionsService } from './accounting/services/accounting.connection.service'; import { MarketingAutomationConnectionsService } from './marketingautomation/services/marketingautomation.connection.service'; import { JwtAuthGuard } from '@@core/auth/guards/jwt-auth.guard'; @@ -69,7 +69,7 @@ export class ConnectionsController { const { projectId, vertical, linkedUserId, providerName, returnUrl } = stateData; switch (vertical.toLowerCase()) { - case ProviderVertical.CRM: + case ConnectorCategory.Crm: const zohoLocation_ = zohoLocation ? zohoLocation : ''; this.crmConnectionsService.handleCRMCallBack( projectId, @@ -79,9 +79,9 @@ export class ConnectionsController { zohoLocation_, ); break; - case ProviderVertical.ATS: + case ConnectorCategory.Ats: break; - case ProviderVertical.Accounting: + case ConnectorCategory.Accounting: this.accountingConnectionsService.handleAccountingCallBack( projectId, linkedUserId, @@ -89,11 +89,11 @@ export class ConnectionsController { code, ); break; - case ProviderVertical.FileStorage: + case ConnectorCategory.FileStorage: break; - case ProviderVertical.HRIS: + case ConnectorCategory.Hris: break; - case ProviderVertical.MarketingAutomation: + case ConnectorCategory.MarketingAutomation: this.marketingAutomationConnectionsService.handleMarketingAutomationCallBack( projectId, linkedUserId, @@ -101,7 +101,7 @@ export class ConnectionsController { code, ); break; - case ProviderVertical.Ticketing: + case ConnectorCategory.Ticketing: this.ticketingConnectionsService.handleTicketingCallBack( projectId, linkedUserId, diff --git a/packages/api/src/@core/connections/crm/services/accelo/accelo.service.ts b/packages/api/src/@core/connections/crm/services/accelo/accelo.service.ts index 26e42aff4..679b1ef54 100644 --- a/packages/api/src/@core/connections/crm/services/accelo/accelo.service.ts +++ b/packages/api/src/@core/connections/crm/services/accelo/accelo.service.ts @@ -14,7 +14,7 @@ import { ServiceRegistry } from '../registry.service'; import { LoggerService } from '@@core/logger/logger.service'; import { OAuth2AuthData, - providersConfig, + CONNECTORS_METADATA, providerToType, } from '@panora/shared'; import { AuthStrategy } from '@panora/shared'; @@ -90,7 +90,7 @@ export class AcceloConnectionService implements ICrmConnectionService { const connection_token = uuidv4(); //get the right BASE URL API const BASE_API_URL = - CREDENTIALS.SUBDOMAIN + providersConfig['crm']['accelo'].urls.apiUrl; + CREDENTIALS.SUBDOMAIN + CONNECTORS_METADATA['crm']['accelo'].urls.apiUrl; if (isNotUnique) { // Update existing connection diff --git a/packages/api/src/@core/connections/crm/services/attio/attio.service.ts b/packages/api/src/@core/connections/crm/services/attio/attio.service.ts index 565d209e6..25d2b0bf7 100644 --- a/packages/api/src/@core/connections/crm/services/attio/attio.service.ts +++ b/packages/api/src/@core/connections/crm/services/attio/attio.service.ts @@ -14,7 +14,7 @@ import { ServiceRegistry } from '../registry.service'; import { LoggerService } from '@@core/logger/logger.service'; import { OAuth2AuthData, - providersConfig, + CONNECTORS_METADATA, providerToType, } from '@panora/shared'; import { AuthStrategy } from '@panora/shared'; @@ -108,7 +108,7 @@ export class AttioConnectionService implements ICrmConnectionService { provider_slug: 'attio', vertical: 'crm', token_type: 'oauth', - account_url: providersConfig['crm']['attio'].urls.apiUrl, + account_url: CONNECTORS_METADATA['crm']['attio'].urls.apiUrl, access_token: this.cryptoService.encrypt(data.access_token), status: 'valid', created_at: new Date(), diff --git a/packages/api/src/@core/connections/crm/services/capsule/capsule.service.ts b/packages/api/src/@core/connections/crm/services/capsule/capsule.service.ts index d49062919..e0b9425d5 100644 --- a/packages/api/src/@core/connections/crm/services/capsule/capsule.service.ts +++ b/packages/api/src/@core/connections/crm/services/capsule/capsule.service.ts @@ -14,7 +14,7 @@ import { import { ServiceRegistry } from '../registry.service'; import { OAuth2AuthData, - providersConfig, + CONNECTORS_METADATA, providerToType, } from '@panora/shared'; import { AuthStrategy } from '@panora/shared'; @@ -94,7 +94,7 @@ export class CapsuleConnectionService implements ICrmConnectionService { data: { access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), - account_url: providersConfig['crm']['capsule'].urls.apiUrl, + account_url: CONNECTORS_METADATA['crm']['capsule'].urls.apiUrl, expiration_timestamp: new Date( new Date().getTime() + Number(data.expires_in) * 1000, ), @@ -110,7 +110,7 @@ export class CapsuleConnectionService implements ICrmConnectionService { provider_slug: 'capsule', vertical: 'crm', token_type: 'oauth', - account_url: providersConfig['crm']['capsule'].urls.apiUrl, + account_url: CONNECTORS_METADATA['crm']['capsule'].urls.apiUrl, access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), expiration_timestamp: new Date( diff --git a/packages/api/src/@core/connections/crm/services/close/close.service.ts b/packages/api/src/@core/connections/crm/services/close/close.service.ts index 57da7967c..e19143386 100644 --- a/packages/api/src/@core/connections/crm/services/close/close.service.ts +++ b/packages/api/src/@core/connections/crm/services/close/close.service.ts @@ -14,7 +14,7 @@ import { import { ServiceRegistry } from '../registry.service'; import { OAuth2AuthData, - providersConfig, + CONNECTORS_METADATA, providerToType, } from '@panora/shared'; import { AuthStrategy } from '@panora/shared'; @@ -98,7 +98,7 @@ export class CloseConnectionService implements ICrmConnectionService { data: { access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), - account_url: providersConfig['crm']['close'].urls.apiUrl, + account_url: CONNECTORS_METADATA['crm']['close'].urls.apiUrl, expiration_timestamp: new Date( new Date().getTime() + Number(data.expires_in) * 1000, ), @@ -114,7 +114,7 @@ export class CloseConnectionService implements ICrmConnectionService { provider_slug: 'close', vertical: 'crm', token_type: 'oauth', - account_url: providersConfig['crm']['close'].urls.apiUrl, + account_url: CONNECTORS_METADATA['crm']['close'].urls.apiUrl, access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), expiration_timestamp: new Date( diff --git a/packages/api/src/@core/connections/crm/services/copper/copper.service.ts b/packages/api/src/@core/connections/crm/services/copper/copper.service.ts index 42dfa4202..a985f315a 100644 --- a/packages/api/src/@core/connections/crm/services/copper/copper.service.ts +++ b/packages/api/src/@core/connections/crm/services/copper/copper.service.ts @@ -14,7 +14,7 @@ import { import { ServiceRegistry } from '../registry.service'; import { OAuth2AuthData, - providersConfig, + CONNECTORS_METADATA, providerToType, } from '@panora/shared'; import { AuthStrategy } from '@panora/shared'; @@ -104,7 +104,7 @@ export class CopperConnectionService implements ICrmConnectionService { provider_slug: 'copper', vertical: 'crm', token_type: 'oauth', - account_url: providersConfig['crm']['copper'].urls.apiUrl, + account_url: CONNECTORS_METADATA['crm']['copper'].urls.apiUrl, access_token: this.cryptoService.encrypt(data.access_token), status: 'valid', created_at: new Date(), diff --git a/packages/api/src/@core/connections/crm/services/hubspot/hubspot.service.ts b/packages/api/src/@core/connections/crm/services/hubspot/hubspot.service.ts index 26ff4b850..458d8aae4 100644 --- a/packages/api/src/@core/connections/crm/services/hubspot/hubspot.service.ts +++ b/packages/api/src/@core/connections/crm/services/hubspot/hubspot.service.ts @@ -14,7 +14,7 @@ import { EncryptionService } from '@@core/encryption/encryption.service'; import { ServiceRegistry } from '../registry.service'; import { OAuth2AuthData, - providersConfig, + CONNECTORS_METADATA, providerToType, } from '@panora/shared'; import { AuthStrategy } from '@panora/shared'; @@ -111,7 +111,7 @@ export class HubspotConnectionService implements ICrmConnectionService { provider_slug: 'hubspot', vertical: 'crm', token_type: 'oauth', - account_url: providersConfig['crm']['hubspot'].urls.apiUrl, + account_url: CONNECTORS_METADATA['crm']['hubspot'].urls.apiUrl, access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), expiration_timestamp: new Date( diff --git a/packages/api/src/@core/connections/crm/services/keap/keap.service.ts b/packages/api/src/@core/connections/crm/services/keap/keap.service.ts index 66772aa8a..9fbcfc27c 100644 --- a/packages/api/src/@core/connections/crm/services/keap/keap.service.ts +++ b/packages/api/src/@core/connections/crm/services/keap/keap.service.ts @@ -14,7 +14,7 @@ import { import { ServiceRegistry } from '../registry.service'; import { OAuth2AuthData, - providersConfig, + CONNECTORS_METADATA, providerToType, } from '@panora/shared'; import { AuthStrategy } from '@panora/shared'; @@ -108,7 +108,7 @@ export class KeapConnectionService implements ICrmConnectionService { provider_slug: 'keap', vertical: 'crm', token_type: 'oauth', - account_url: providersConfig['crm']['keap'].urls.apiUrl, + account_url: CONNECTORS_METADATA['crm']['keap'].urls.apiUrl, access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), expiration_timestamp: new Date( diff --git a/packages/api/src/@core/connections/crm/services/pipedrive/pipedrive.service.ts b/packages/api/src/@core/connections/crm/services/pipedrive/pipedrive.service.ts index e8eb337ec..81d14c787 100644 --- a/packages/api/src/@core/connections/crm/services/pipedrive/pipedrive.service.ts +++ b/packages/api/src/@core/connections/crm/services/pipedrive/pipedrive.service.ts @@ -14,7 +14,7 @@ import { EncryptionService } from '@@core/encryption/encryption.service'; import { ServiceRegistry } from '../registry.service'; import { OAuth2AuthData, - providersConfig, + CONNECTORS_METADATA, providerToType, } from '@panora/shared'; import { AuthStrategy } from '@panora/shared'; @@ -108,7 +108,7 @@ export class PipedriveConnectionService implements ICrmConnectionService { provider_slug: 'pipedrive', vertical: 'crm', token_type: 'oauth', - account_url: providersConfig['crm']['pipedrive'].urls.apiUrl, + account_url: CONNECTORS_METADATA['crm']['pipedrive'].urls.apiUrl, access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), expiration_timestamp: new Date( diff --git a/packages/api/src/@core/connections/crm/services/teamleader/teamleader.service.ts b/packages/api/src/@core/connections/crm/services/teamleader/teamleader.service.ts index 48f09a133..d46bfe6e5 100644 --- a/packages/api/src/@core/connections/crm/services/teamleader/teamleader.service.ts +++ b/packages/api/src/@core/connections/crm/services/teamleader/teamleader.service.ts @@ -14,7 +14,7 @@ import { import { ServiceRegistry } from '../registry.service'; import { OAuth2AuthData, - providersConfig, + CONNECTORS_METADATA, providerToType, } from '@panora/shared'; import { AuthStrategy } from '@panora/shared'; @@ -94,7 +94,7 @@ export class TeamleaderConnectionService implements ICrmConnectionService { data: { access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), - account_url: providersConfig['crm']['teamleader'].urls.apiUrl, + account_url: CONNECTORS_METADATA['crm']['teamleader'].urls.apiUrl, expiration_timestamp: new Date( new Date().getTime() + Number(data.expires_in) * 1000, ), diff --git a/packages/api/src/@core/connections/crm/services/teamwork/teamwork.service.ts b/packages/api/src/@core/connections/crm/services/teamwork/teamwork.service.ts index efab60ab2..04e8f0b5f 100644 --- a/packages/api/src/@core/connections/crm/services/teamwork/teamwork.service.ts +++ b/packages/api/src/@core/connections/crm/services/teamwork/teamwork.service.ts @@ -14,7 +14,7 @@ import { import { ServiceRegistry } from '../registry.service'; import { OAuth2AuthData, - providersConfig, + CONNECTORS_METADATA, providerToType, } from '@panora/shared'; import { AuthStrategy } from '@panora/shared'; @@ -83,7 +83,7 @@ export class TeamworkConnectionService implements ICrmConnectionService { const connection_token = uuidv4(); //get the right BASE URL API const BASE_API_URL = - CREDENTIALS.SUBDOMAIN + providersConfig['crm']['teamwork'].urls.apiUrl; + CREDENTIALS.SUBDOMAIN + CONNECTORS_METADATA['crm']['teamwork'].urls.apiUrl; if (isNotUnique) { db_res = await this.prisma.connections.update({ diff --git a/packages/api/src/@core/connections/crm/services/zendesk/zendesk.service.ts b/packages/api/src/@core/connections/crm/services/zendesk/zendesk.service.ts index a57205c19..351e81088 100644 --- a/packages/api/src/@core/connections/crm/services/zendesk/zendesk.service.ts +++ b/packages/api/src/@core/connections/crm/services/zendesk/zendesk.service.ts @@ -14,7 +14,7 @@ import { EncryptionService } from '@@core/encryption/encryption.service'; import { ServiceRegistry } from '../registry.service'; import { OAuth2AuthData, - providersConfig, + CONNECTORS_METADATA, providerToType, } from '@panora/shared'; import { AuthStrategy } from '@panora/shared'; @@ -112,7 +112,7 @@ export class ZendeskConnectionService implements ICrmConnectionService { provider_slug: 'zendesk', vertical: 'crm', token_type: 'oauth', - account_url: providersConfig['crm']['zendesk'].urls.apiUrl, + account_url: CONNECTORS_METADATA['crm']['zendesk'].urls.apiUrl, access_token: this.cryptoService.encrypt(data.access_token), refresh_token: data.refresh_token ? this.cryptoService.encrypt(data.refresh_token) diff --git a/packages/api/src/@core/connections/crm/services/zoho/zoho.service.ts b/packages/api/src/@core/connections/crm/services/zoho/zoho.service.ts index 57eb20088..91f0da6c6 100644 --- a/packages/api/src/@core/connections/crm/services/zoho/zoho.service.ts +++ b/packages/api/src/@core/connections/crm/services/zoho/zoho.service.ts @@ -14,7 +14,7 @@ import { EncryptionService } from '@@core/encryption/encryption.service'; import { ServiceRegistry } from '../registry.service'; import { OAuth2AuthData, - providersConfig, + CONNECTORS_METADATA, providerToType, } from '@panora/shared'; import { AuthStrategy } from '@panora/shared'; @@ -134,7 +134,7 @@ export class ZohoConnectionService implements ICrmConnectionService { ), status: 'valid', created_at: new Date(), - account_url: apiDomain + providersConfig['crm']['zoho'].urls.apiUrl, + account_url: apiDomain + CONNECTORS_METADATA['crm']['zoho'].urls.apiUrl, }, }); } else { @@ -160,7 +160,7 @@ export class ZohoConnectionService implements ICrmConnectionService { linked_users: { connect: { id_linked_user: linkedUserId }, }, - account_url: apiDomain + providersConfig['crm']['zoho'].urls.apiUrl, + account_url: apiDomain + CONNECTORS_METADATA['crm']['zoho'].urls.apiUrl, }, }); } diff --git a/packages/api/src/@core/connections/marketingautomation/services/getresponse/getresponse.service.ts b/packages/api/src/@core/connections/marketingautomation/services/getresponse/getresponse.service.ts index 6bd315886..5dfd1fd24 100644 --- a/packages/api/src/@core/connections/marketingautomation/services/getresponse/getresponse.service.ts +++ b/packages/api/src/@core/connections/marketingautomation/services/getresponse/getresponse.service.ts @@ -12,7 +12,7 @@ import { IMarketingAutomationConnectionService, } from '../../types'; import { ServiceRegistry } from '../registry.service'; -import { AuthStrategy, providersConfig } from '@panora/shared'; +import { AuthStrategy, CONNECTORS_METADATA } from '@panora/shared'; import { OAuth2AuthData, providerToType } from '@panora/shared'; import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service'; @@ -98,7 +98,7 @@ export class GetresponseConnectionService access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), account_url: - providersConfig['marketingautomation']['getresponse'].urls.apiUrl, + CONNECTORS_METADATA['marketingautomation']['getresponse'].urls.apiUrl, expiration_timestamp: new Date( new Date().getTime() + Number(data.expires_in) * 1000, ), @@ -115,7 +115,7 @@ export class GetresponseConnectionService vertical: 'marketingautomation', token_type: 'oauth', account_url: - providersConfig['marketingautomation']['getresponse'].urls.apiUrl, + CONNECTORS_METADATA['marketingautomation']['getresponse'].urls.apiUrl, access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), expiration_timestamp: new Date( diff --git a/packages/api/src/@core/connections/marketingautomation/services/podium/podium.service.ts b/packages/api/src/@core/connections/marketingautomation/services/podium/podium.service.ts index 91bc1105f..9e997d3be 100644 --- a/packages/api/src/@core/connections/marketingautomation/services/podium/podium.service.ts +++ b/packages/api/src/@core/connections/marketingautomation/services/podium/podium.service.ts @@ -12,7 +12,7 @@ import { IMarketingAutomationConnectionService, } from '../../types'; import { ServiceRegistry } from '../registry.service'; -import { AuthStrategy, providersConfig } from '@panora/shared'; +import { AuthStrategy, CONNECTORS_METADATA } from '@panora/shared'; import { OAuth2AuthData, providerToType } from '@panora/shared'; import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service'; @@ -94,7 +94,7 @@ export class PodiumConnectionService access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), account_url: - providersConfig['marketingautomation']['podium'].urls.apiUrl, + CONNECTORS_METADATA['marketingautomation']['podium'].urls.apiUrl, expiration_timestamp: new Date( new Date().getTime() + 10 * 60 * 60 * 1000, ), @@ -111,7 +111,7 @@ export class PodiumConnectionService vertical: 'marketingautomation', token_type: 'oauth', account_url: - providersConfig['marketingautomation']['pdoum'].urls.apiUrl, + CONNECTORS_METADATA['marketingautomation']['pdoum'].urls.apiUrl, access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), expiration_timestamp: new Date( diff --git a/packages/api/src/@core/connections/ticketing/services/aha/aha.service.ts b/packages/api/src/@core/connections/ticketing/services/aha/aha.service.ts index 3a0f241c8..43ee059f8 100644 --- a/packages/api/src/@core/connections/ticketing/services/aha/aha.service.ts +++ b/packages/api/src/@core/connections/ticketing/services/aha/aha.service.ts @@ -12,7 +12,7 @@ import { ITicketingConnectionService, } from '../../types'; import { ServiceRegistry } from '../registry.service'; -import { AuthStrategy, providersConfig } from '@panora/shared'; +import { AuthStrategy, CONNECTORS_METADATA } from '@panora/shared'; import { OAuth2AuthData, providerToType } from '@panora/shared'; import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service'; @@ -82,7 +82,7 @@ export class AhaConnectionService implements ITicketingConnectionService { const connection_token = uuidv4(); //get the right BASE URL API const BASE_API_URL = - CREDENTIALS.SUBDOMAIN + providersConfig['ticketing']['aha'].urls.apiUrl; + CREDENTIALS.SUBDOMAIN + CONNECTORS_METADATA['ticketing']['aha'].urls.apiUrl; if (isNotUnique) { db_res = await this.prisma.connections.update({ diff --git a/packages/api/src/@core/connections/ticketing/services/gitlab/gitlab.service.ts b/packages/api/src/@core/connections/ticketing/services/gitlab/gitlab.service.ts index 929c6de18..859f56605 100644 --- a/packages/api/src/@core/connections/ticketing/services/gitlab/gitlab.service.ts +++ b/packages/api/src/@core/connections/ticketing/services/gitlab/gitlab.service.ts @@ -12,7 +12,7 @@ import { ITicketingConnectionService, } from '../../types'; import { ServiceRegistry } from '../registry.service'; -import { AuthStrategy, providersConfig } from '@panora/shared'; +import { AuthStrategy, CONNECTORS_METADATA } from '@panora/shared'; import { OAuth2AuthData, providerToType } from '@panora/shared'; import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service'; @@ -107,7 +107,7 @@ export class GitlabConnectionService implements ITicketingConnectionService { connection_token: connection_token, provider_slug: 'gitlab', vertical: 'ticketing', - account_url: providersConfig['ticketing']['gitlab'].urls.apiUrl, + account_url: CONNECTORS_METADATA['ticketing']['gitlab'].urls.apiUrl, token_type: 'oauth', access_token: this.cryptoService.encrypt(data.access_token), refresh_token: this.cryptoService.encrypt(data.refresh_token), diff --git a/packages/api/src/@core/connections/ticketing/services/gorgias/gorgias.service.ts b/packages/api/src/@core/connections/ticketing/services/gorgias/gorgias.service.ts index 1d4b55597..86ba75ed7 100644 --- a/packages/api/src/@core/connections/ticketing/services/gorgias/gorgias.service.ts +++ b/packages/api/src/@core/connections/ticketing/services/gorgias/gorgias.service.ts @@ -14,7 +14,7 @@ import { import { ServiceRegistry } from '../registry.service'; import { OAuth2AuthData, - providersConfig, + CONNECTORS_METADATA, providerToType, } from '@panora/shared'; import { AuthStrategy } from '@panora/shared'; @@ -90,7 +90,7 @@ export class GorgiasConnectionService implements ITicketingConnectionService { const connection_token = uuidv4(); const BASE_API_URL = CREDENTIALS.SUBDOMAIN + - providersConfig['ticketing']['gorgias'].urls.apiUrl; + CONNECTORS_METADATA['ticketing']['gorgias'].urls.apiUrl; if (isNotUnique) { db_res = await this.prisma.connections.update({ diff --git a/packages/api/src/@core/connections/ticketing/services/zendesk/zendesk.service.ts b/packages/api/src/@core/connections/ticketing/services/zendesk/zendesk.service.ts index 3944b4314..df782d5a1 100644 --- a/packages/api/src/@core/connections/ticketing/services/zendesk/zendesk.service.ts +++ b/packages/api/src/@core/connections/ticketing/services/zendesk/zendesk.service.ts @@ -12,7 +12,7 @@ import { ITicketingConnectionService, } from '../../types'; import { ServiceRegistry } from '../registry.service'; -import { AuthStrategy, providersConfig } from '@panora/shared'; +import { AuthStrategy, CONNECTORS_METADATA } from '@panora/shared'; import { OAuth2AuthData, providerToType } from '@panora/shared'; import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service'; @@ -83,7 +83,7 @@ export class ZendeskConnectionService implements ITicketingConnectionService { const connection_token = uuidv4(); const BASE_API_URL = CREDENTIALS.SUBDOMAIN + - providersConfig['ticketing']['zendesk'].urls.apiUrl; + CONNECTORS_METADATA['ticketing']['zendesk'].urls.apiUrl; if (isNotUnique) { db_res = await this.prisma.connections.update({ diff --git a/packages/api/src/@core/core.module.ts b/packages/api/src/@core/core.module.ts index cb2233734..a06fc0f6d 100644 --- a/packages/api/src/@core/core.module.ts +++ b/packages/api/src/@core/core.module.ts @@ -13,6 +13,7 @@ import { EnvironmentModule } from './environment/environment.module'; import { EncryptionService } from './encryption/encryption.service'; import { ConnectionsStrategiesModule } from './connections-strategies/connections-strategies.module'; import { SyncModule } from './sync/sync.module'; +import { ProjectConnectorsModule } from './project-connectors/project-connectors.module'; @Module({ imports: [ @@ -29,6 +30,7 @@ import { SyncModule } from './sync/sync.module'; EnvironmentModule, ConnectionsStrategiesModule, SyncModule, + ProjectConnectorsModule, ], exports: [ AuthModule, @@ -44,6 +46,7 @@ import { SyncModule } from './sync/sync.module'; EnvironmentModule, ConnectionsStrategiesModule, SyncModule, + ProjectConnectorsModule, ], providers: [EncryptionService], }) diff --git a/packages/api/src/@core/field-mapping/field-mapping.service.ts b/packages/api/src/@core/field-mapping/field-mapping.service.ts index 84d9b942b..a8b63d4af 100644 --- a/packages/api/src/@core/field-mapping/field-mapping.service.ts +++ b/packages/api/src/@core/field-mapping/field-mapping.service.ts @@ -10,7 +10,7 @@ import axios from 'axios'; import { ActionType, handleServiceError } from '@@core/utils/errors'; import { CrmObject } from '@crm/@lib/@types'; import { EncryptionService } from '@@core/encryption/encryption.service'; -import { providersConfig } from '@panora/shared'; +import { CONNECTORS_METADATA } from '@panora/shared'; @Injectable() export class FieldMappingService { @@ -141,7 +141,7 @@ export class FieldMappingService { vertical: vertical.toLowerCase(), }, }); - const provider = providersConfig[vertical][providerId.toLowerCase()]; + const provider = CONNECTORS_METADATA[vertical][providerId.toLowerCase()]; if (!provider.urls.apiUrl || !provider.urls.customPropertiesUrl) throw new Error('proivder urls are invalid'); diff --git a/packages/api/src/@core/linked-users/dto/create-linked-user.dto.ts b/packages/api/src/@core/linked-users/dto/create-linked-user.dto.ts index 8c8c4db51..a2251914e 100644 --- a/packages/api/src/@core/linked-users/dto/create-linked-user.dto.ts +++ b/packages/api/src/@core/linked-users/dto/create-linked-user.dto.ts @@ -8,3 +8,12 @@ export class CreateLinkedUserDto { @ApiProperty() id_project: string; } + +export class CreateBatchLinkedUserDto { + @ApiProperty() + linked_user_origin_ids: string[]; + @ApiProperty() + alias: string; + @ApiProperty() + id_project: string; +} diff --git a/packages/api/src/@core/linked-users/linked-users.controller.ts b/packages/api/src/@core/linked-users/linked-users.controller.ts index 05068a15e..7c65c6b22 100644 --- a/packages/api/src/@core/linked-users/linked-users.controller.ts +++ b/packages/api/src/@core/linked-users/linked-users.controller.ts @@ -9,7 +9,10 @@ import { } from '@nestjs/common'; import { LinkedUsersService } from './linked-users.service'; import { LoggerService } from '../logger/logger.service'; -import { CreateLinkedUserDto } from './dto/create-linked-user.dto'; +import { + CreateBatchLinkedUserDto, + CreateLinkedUserDto, +} from './dto/create-linked-user.dto'; import { ApiBody, ApiOperation, @@ -38,6 +41,18 @@ export class LinkedUsersController { return this.linkedUsersService.addLinkedUser(linkedUserCreateDto); } + @ApiOperation({ + operationId: 'addBatchLinkedUsers', + summary: 'Add Batch Linked Users', + }) + @ApiBody({ type: CreateBatchLinkedUserDto }) + @ApiResponse({ status: 201 }) + //@UseGuards(JwtAuthGuard) + @Post('batch') + addBatchLinkedUsers(@Body() data: CreateBatchLinkedUserDto) { + return this.linkedUsersService.addBatchLinkedUsers(data); + } + @ApiOperation({ operationId: 'getLinkedUsers', summary: 'Retrieve Linked Users', diff --git a/packages/api/src/@core/linked-users/linked-users.service.ts b/packages/api/src/@core/linked-users/linked-users.service.ts index 66589fc89..86db512da 100644 --- a/packages/api/src/@core/linked-users/linked-users.service.ts +++ b/packages/api/src/@core/linked-users/linked-users.service.ts @@ -1,5 +1,8 @@ import { Injectable } from '@nestjs/common'; -import { CreateLinkedUserDto } from './dto/create-linked-user.dto'; +import { + CreateBatchLinkedUserDto, + CreateLinkedUserDto, +} from './dto/create-linked-user.dto'; import { PrismaService } from '../prisma/prisma.service'; import { LoggerService } from '../logger/logger.service'; import { v4 as uuidv4 } from 'uuid'; @@ -58,4 +61,25 @@ export class LinkedUsersService { handleServiceError(error, this.logger); } } + async addBatchLinkedUsers(data: CreateBatchLinkedUserDto) { + try { + const { linked_user_origin_ids, alias, id_project } = data; + + const linkedUsersData = linked_user_origin_ids.map((id) => ({ + id_linked_user: uuidv4(), // Ensure each user gets a unique ID + linked_user_origin_id: id, + alias, + id_project, + })); + + const res = await this.prisma.linked_users.createMany({ + data: linkedUsersData, + skipDuplicates: true, // Optional: skip entries if they already exist + }); + + return res; + } catch (error) { + handleServiceError(error, this.logger); + } + } } diff --git a/packages/api/src/@core/passthrough/passthrough.service.ts b/packages/api/src/@core/passthrough/passthrough.service.ts index c489be02d..403ae710f 100644 --- a/packages/api/src/@core/passthrough/passthrough.service.ts +++ b/packages/api/src/@core/passthrough/passthrough.service.ts @@ -7,7 +7,7 @@ import { v4 as uuidv4 } from 'uuid'; import { LoggerService } from '@@core/logger/logger.service'; import { handleServiceError } from '@@core/utils/errors'; import { EncryptionService } from '@@core/encryption/encryption.service'; -import { providersConfig } from '@panora/shared'; +import { CONNECTORS_METADATA } from '@panora/shared'; @Injectable() export class PassthroughService { @@ -43,7 +43,7 @@ export class PassthroughService { }); const intId = integrationId.toLowerCase(); const providerUrl = - providersConfig[vertical.toLowerCase()][intId].urls.apiUrl; + CONNECTORS_METADATA[vertical.toLowerCase()][intId].urls.apiUrl; const BASE_URL = `${providerUrl}${path}`; const connection = await this.prisma.connections.findFirst({ where: { diff --git a/packages/api/src/@core/project-connectors/dto/project-connectors.dto.ts b/packages/api/src/@core/project-connectors/dto/project-connectors.dto.ts new file mode 100644 index 000000000..33b3379e0 --- /dev/null +++ b/packages/api/src/@core/project-connectors/dto/project-connectors.dto.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsBoolean, IsString } from 'class-validator'; + +export class ProjectConnectorsDto { + @ApiProperty() + @IsString() + column: string; + + @ApiProperty() + @IsBoolean() + status: boolean; +} diff --git a/packages/api/src/@core/project-connectors/project-connectors.controller.ts b/packages/api/src/@core/project-connectors/project-connectors.controller.ts new file mode 100644 index 000000000..a7cb29f6e --- /dev/null +++ b/packages/api/src/@core/project-connectors/project-connectors.controller.ts @@ -0,0 +1,82 @@ +import { + Body, + Controller, + Get, + Post, + Query, + UseGuards, + Request, +} from '@nestjs/common'; +import { LoggerService } from '../logger/logger.service'; +import { + ApiBody, + ApiOperation, + ApiQuery, + ApiResponse, + ApiTags, +} from '@nestjs/swagger'; +import { ProjectConnectorsService } from './project-connectors.service'; +import { ProjectConnectorsDto } from './dto/project-connectors.dto'; +import { JwtAuthGuard } from '@@core/auth/guards/jwt-auth.guard'; +export interface TypeCustom { + id_project: string; + crm_hubspot: boolean; + crm_zoho: boolean; + crm_zendesk: boolean; + crm_pipedrive: boolean; + crm_attio: boolean; + tcg_zendesk: boolean; + tcg_gorgias: boolean; + tcg_front: boolean; + tcg_jira: boolean; + tcg_gitlab: boolean; +} +@ApiTags('project-connectors') +@Controller('project-connectors') +export class ProjectConnectorsController { + constructor( + private readonly projectConnectorsService: ProjectConnectorsService, + private logger: LoggerService, + ) { + this.logger.setContext(ProjectConnectorsController.name); + } + + @ApiOperation({ + operationId: 'updateConnectorsToProject', + summary: 'Update Connectors for a project', + }) + @ApiBody({ type: ProjectConnectorsDto }) + @ApiResponse({ status: 201 }) + @UseGuards(JwtAuthGuard) + @Post() + async updateConnectorsToProject( + @Body() projectConnectorsDto: ProjectConnectorsDto, + @Request() req: any, + ) { + const { id_project } = req.user; + const { column, status } = projectConnectorsDto; + return await this.projectConnectorsService.updateProjectConnectors( + column, + status, + id_project, + ); + } + + @Post('create') + async createConnectorsToProject(@Body() data: TypeCustom) { + return await this.projectConnectorsService.createProjectConnectors(data); + } + + // It should be public API and don't have to add AuthGuard + // TODO: add admin control + @ApiOperation({ + operationId: 'getConnectorsFromProject', + summary: 'Retrieve connectors by Project Id', + }) + @ApiQuery({ name: 'getConnectorsFromProject', required: true, type: String }) + @ApiResponse({ status: 200 }) + @Get() + async getConnectorsFromProject(@Query('projectId') id: string) { + return await this.projectConnectorsService.getConnectorsByProjectId(id); + } +} diff --git a/packages/api/src/@core/project-connectors/project-connectors.module.ts b/packages/api/src/@core/project-connectors/project-connectors.module.ts new file mode 100644 index 000000000..afb2346e6 --- /dev/null +++ b/packages/api/src/@core/project-connectors/project-connectors.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { ProjectConnectorsService } from './project-connectors.service'; +import { ProjectConnectorsController } from './project-connectors.controller'; +import { LoggerService } from '../logger/logger.service'; +import { PrismaService } from '../prisma/prisma.service'; + +@Module({ + providers: [ProjectConnectorsService, LoggerService, PrismaService], + controllers: [ProjectConnectorsController], +}) +export class ProjectConnectorsModule {} diff --git a/packages/api/src/@core/project-connectors/project-connectors.service.ts b/packages/api/src/@core/project-connectors/project-connectors.service.ts new file mode 100644 index 000000000..ebcc82a2b --- /dev/null +++ b/packages/api/src/@core/project-connectors/project-connectors.service.ts @@ -0,0 +1,104 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { PrismaService } from '../prisma/prisma.service'; +import { LoggerService } from '../logger/logger.service'; +import { v4 as uuidv4 } from 'uuid'; +import { handleServiceError } from '@@core/utils/errors'; +import { TypeCustom } from './project-connectors.controller'; + +@Injectable() +export class ProjectConnectorsService { + constructor(private prisma: PrismaService, private logger: LoggerService) { + this.logger.setContext(ProjectConnectorsService.name); + } + + // TODO: create a function that accepts a "vertical_connector" and insert it inside the table for all projects setting to true by default + // it would be used when a new connector is built and needs to be insert inside our tbale + async updateProjectConnectors( + column: string, + status: boolean, + id_project: string, + ) { + try { + const existingPConnectors = + await this.prisma.project_connectors.findFirst({ + where: { + id_project: id_project, + }, + }); + + if (!existingPConnectors) { + throw new Error( + `No project connector entry found for project ${id_project}`, + ); + } + + const updateData: any = { + [column]: status, // Use computed property names to set the column dynamically + }; + + const res = await this.prisma.project_connectors.update({ + where: { + id_project_connector: existingPConnectors.id_project_connector, + }, + data: updateData, + }); + return res; + } catch (error) { + handleServiceError(error, this.logger); + } + } + + async createProjectConnectors(data: TypeCustom) { + try { + const updateData: any = { + id_project_connector: uuidv4(), + id_project: data.id_project, + crm_hubspot: data.crm_hubspot, + crm_zoho: data.crm_zoho, + crm_zendesk: data.crm_zendesk, + crm_pipedrive: data.crm_pipedrive, + crm_attio: data.crm_attio, + tcg_zendesk: data.tcg_zendesk, + tcg_gorgias: data.tcg_gorgias, + tcg_front: data.tcg_front, + tcg_jira: data.tcg_jira, + tcg_gitlab: data.tcg_gitlab, + }; + + const res = await this.prisma.project_connectors.create({ + data: updateData, + }); + return res; + } catch (error) { + handleServiceError(error, this.logger); + } + } + + async getConnectorsByProjectId(id_project: string) { + try { + const project = await this.prisma.projects.findFirst({ + where: { + id_project, + }, + }); + + if (!project) { + throw new NotFoundException('Project does not exist!'); + } + + const res = await this.prisma.project_connectors.findFirst({ + where: { + id_project: id_project, + }, + }); + if (!res) { + throw new NotFoundException( + 'Connectors not found for current project!', + ); + } + return res; + } catch (error) { + handleServiceError(error, this.logger); + } + } +} diff --git a/packages/api/src/@core/tasks/tasks.service.ts b/packages/api/src/@core/tasks/tasks.service.ts index dbbbba2bf..22b6bbde2 100644 --- a/packages/api/src/@core/tasks/tasks.service.ts +++ b/packages/api/src/@core/tasks/tasks.service.ts @@ -4,7 +4,7 @@ import { Cron, CronExpression } from '@nestjs/schedule'; import { PrismaService } from '../prisma/prisma.service'; import { CrmConnectionsService } from '../connections/crm/services/crm.connection.service'; import { LoggerService } from '@@core/logger/logger.service'; -import { ProviderVertical } from '@panora/shared'; +import { ConnectorCategory } from '@panora/shared'; import { AccountingConnectionsService } from '@@core/connections/accounting/services/accounting.connection.service'; import { MarketingAutomationConnectionsService } from '@@core/connections/marketingautomation/services/marketingautomation.connection.service'; import { TicketingConnectionsService } from '@@core/connections/ticketing/services/ticketing.connection.service'; @@ -18,7 +18,7 @@ export class TasksService implements OnModuleInit { private readonly accountingConnectionsService: AccountingConnectionsService, private readonly marketingAutomationConnectionsService: MarketingAutomationConnectionsService, private logger: LoggerService, - ) { } + ) {} onModuleInit() { this.handleCron(); @@ -43,7 +43,7 @@ export class TasksService implements OnModuleInit { connection.provider_slug == 'zoho' ? connection.account_url : ''; switch (connection.vertical) { - case ProviderVertical.CRM: + case ConnectorCategory.Crm: await this.crmConnectionsService.handleCRMTokensRefresh( connection.id_connection, connection.provider_slug, @@ -53,10 +53,10 @@ export class TasksService implements OnModuleInit { ); break; - case ProviderVertical.ATS: + case ConnectorCategory.Ats: break; - case ProviderVertical.Accounting: + case ConnectorCategory.Accounting: this.accountingConnectionsService.handleAccountingTokensRefresh( connection.id_connection, connection.provider_slug, @@ -66,13 +66,13 @@ export class TasksService implements OnModuleInit { ); break; - case ProviderVertical.FileStorage: + case ConnectorCategory.FileStorage: break; - case ProviderVertical.HRIS: + case ConnectorCategory.Hris: break; - case ProviderVertical.MarketingAutomation: + case ConnectorCategory.MarketingAutomation: this.marketingAutomationConnectionsService.handleMarketingAutomationTokensRefresh( connection.id_connection, connection.provider_slug, @@ -82,7 +82,7 @@ export class TasksService implements OnModuleInit { ); break; - case ProviderVertical.Ticketing: + case ConnectorCategory.Ticketing: this.ticketingConnectionsService.handleTicketingTokensRefresh( connection.id_connection, connection.provider_slug, @@ -92,7 +92,6 @@ export class TasksService implements OnModuleInit { ); break; } - } } } diff --git a/packages/api/src/@core/utils/unification/desunify.ts b/packages/api/src/@core/utils/unification/desunify.ts index c825d5e8f..f01d3d048 100644 --- a/packages/api/src/@core/utils/unification/desunify.ts +++ b/packages/api/src/@core/utils/unification/desunify.ts @@ -4,7 +4,7 @@ import { desunifyCrm } from '@crm/@lib/@unification'; import { TicketingObject } from '@ticketing/@lib/@types'; import { desunifyTicketing } from '@ticketing/@lib/@unification'; import { DesunifyReturnType } from '../types/desunify.input'; -import { ProviderVertical } from '@panora/shared'; +import { ConnectorCategory } from '@panora/shared'; /* to insert data @@ -30,7 +30,7 @@ export async function desunify({ }): Promise { let targetType_; switch (vertical.toLowerCase()) { - case ProviderVertical.CRM: + case ConnectorCategory.Crm: targetType_ = targetType as CrmObject; return desunifyCrm({ sourceObject, @@ -38,17 +38,17 @@ export async function desunify({ providerName, customFieldMappings, }); - case ProviderVertical.ATS: + case ConnectorCategory.Ats: break; - case ProviderVertical.Accounting: + case ConnectorCategory.Accounting: break; - case ProviderVertical.FileStorage: + case ConnectorCategory.FileStorage: break; - case ProviderVertical.HRIS: + case ConnectorCategory.Hris: break; - case ProviderVertical.MarketingAutomation: + case ConnectorCategory.MarketingAutomation: break; - case ProviderVertical.Ticketing: + case ConnectorCategory.Ticketing: targetType_ = targetType as TicketingObject; return desunifyTicketing({ sourceObject, diff --git a/packages/api/src/@core/utils/unification/unify.ts b/packages/api/src/@core/utils/unification/unify.ts index 008b35bf2..5c651fde0 100644 --- a/packages/api/src/@core/utils/unification/unify.ts +++ b/packages/api/src/@core/utils/unification/unify.ts @@ -4,7 +4,7 @@ import { unifyCrm } from '@crm/@lib/@unification'; import { TicketingObject } from '@ticketing/@lib/@types'; import { unifyTicketing } from '@ticketing/@lib/@unification'; import { UnifySourceType } from '../types/unify.output'; -import { ProviderVertical } from '@panora/shared'; +import { ConnectorCategory } from '@panora/shared'; /* to fetch data @@ -31,7 +31,7 @@ export async function unify({ if (sourceObject == null) return []; let targetType_; switch (vertical.toLowerCase()) { - case ProviderVertical.CRM: + case ConnectorCategory.Crm: targetType_ = targetType as CrmObject; return unifyCrm({ sourceObject, @@ -39,17 +39,17 @@ export async function unify({ providerName, customFieldMappings, }); - case ProviderVertical.ATS: + case ConnectorCategory.Ats: break; - case ProviderVertical.Accounting: + case ConnectorCategory.Accounting: break; - case ProviderVertical.FileStorage: + case ConnectorCategory.FileStorage: break; - case ProviderVertical.HRIS: + case ConnectorCategory.Hris: break; - case ProviderVertical.MarketingAutomation: + case ConnectorCategory.MarketingAutomation: break; - case ProviderVertical.Ticketing: + case ConnectorCategory.Ticketing: targetType_ = targetType as TicketingObject; return unifyTicketing({ sourceObject, diff --git a/packages/api/swagger/swagger-spec.json b/packages/api/swagger/swagger-spec.json index 346277443..5e1456392 100644 --- a/packages/api/swagger/swagger-spec.json +++ b/packages/api/swagger/swagger-spec.json @@ -515,6 +515,31 @@ ] } }, + "/linked-users/batch": { + "post": { + "operationId": "addBatchLinkedUsers", + "summary": "Add Batch Linked Users", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateBatchLinkedUserDto" + } + } + } + }, + "responses": { + "201": { + "description": "" + } + }, + "tags": [ + "linked-users" + ] + } + }, "/linked-users/single": { "get": { "operationId": "getLinkedUser", @@ -4249,6 +4274,75 @@ ] } }, + "/project-connectors": { + "post": { + "operationId": "updateConnectorsToProject", + "summary": "Update Connectors for a project", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectConnectorsDto" + } + } + } + }, + "responses": { + "201": { + "description": "" + } + }, + "tags": [ + "project-connectors" + ] + }, + "get": { + "operationId": "getConnectorsFromProject", + "summary": "Retrieve connectors by Project Id", + "parameters": [ + { + "name": "projectId", + "required": true, + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "getConnectorsFromProject", + "required": true, + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "" + } + }, + "tags": [ + "project-connectors" + ] + } + }, + "/project-connectors/create": { + "post": { + "operationId": "ProjectConnectorsController_createConnectorsToProject", + "parameters": [], + "responses": { + "201": { + "description": "" + } + }, + "tags": [ + "project-connectors" + ] + } + }, "/ticketing/attachments": { "get": { "operationId": "getAttachments", @@ -4726,6 +4820,28 @@ "id_project" ] }, + "CreateBatchLinkedUserDto": { + "type": "object", + "properties": { + "linked_user_origin_ids": { + "type": "array", + "items": { + "type": "string" + } + }, + "alias": { + "type": "string" + }, + "id_project": { + "type": "string" + } + }, + "required": [ + "linked_user_origin_ids", + "alias", + "id_project" + ] + }, "CreateOrganizationDto": { "type": "object", "properties": { @@ -6206,6 +6322,21 @@ "field_mappings" ] }, + "ProjectConnectorsDto": { + "type": "object", + "properties": { + "column": { + "type": "string" + }, + "status": { + "type": "boolean" + } + }, + "required": [ + "column", + "status" + ] + }, "UnifiedAttachmentInput": { "type": "object", "properties": { diff --git a/packages/shared/src/authUrl.ts b/packages/shared/src/authUrl.ts index 79cda67de..bb2463234 100644 --- a/packages/shared/src/authUrl.ts +++ b/packages/shared/src/authUrl.ts @@ -1,5 +1,8 @@ +import { CONNECTORS_METADATA } from './connectors/metadata'; import { OAuth2AuthData, providerToType } from './envConfig'; -import { AuthStrategy, providersConfig, ProviderConfig } from './utils'; +import { AuthStrategy, ProviderConfig } from './types'; +import { randomString } from './utils'; + interface AuthParams { projectId: string; linkedUserId: string; @@ -9,16 +12,6 @@ interface AuthParams { vertical: string; } -const randomString = () => { - const charSet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - let result = ''; - for (let i = 0; i < length; i++) { - const randomIndex = Math.floor(Math.random() * charSet.length); - result += charSet[randomIndex]; - } - return result; -} - // make sure to check wether its api_key or oauth2 to build the right auth // make sure to check if client has own credentials to connect or panora managed ones export const constructAuthUrl = async ({ projectId, linkedUserId, providerName, returnUrl, apiUrl, vertical }: AuthParams) => { @@ -26,12 +19,12 @@ export const constructAuthUrl = async ({ projectId, linkedUserId, providerName, const state = encodeURIComponent(JSON.stringify({ projectId, linkedUserId, providerName, vertical, returnUrl })); // console.log('State : ', JSON.stringify({ projectId, linkedUserId, providerName, vertical, returnUrl })); // console.log('encodedRedirect URL : ', encodedRedirectUrl); - // const vertical = findProviderVertical(providerName); + // const vertical = findConnectorCategory(providerName); if (vertical == null) { throw new Error('vertical is null'); } - const config = providersConfig[vertical.toLowerCase()][providerName]; + const config = CONNECTORS_METADATA[vertical.toLowerCase()][providerName]; if (!config) { throw new Error(`Unsupported provider: ${providerName}`); } diff --git a/packages/shared/src/categories.ts b/packages/shared/src/categories.ts new file mode 100644 index 000000000..3dacfa892 --- /dev/null +++ b/packages/shared/src/categories.ts @@ -0,0 +1,11 @@ +export enum ConnectorCategory { + Crm = 'crm', + Hris = 'hris', + Ats = 'ats', + Accounting = 'accounting', + Ticketing = 'ticketing', + MarketingAutomation = 'marketingautomation', + FileStorage = 'filestorage', +} + +export const categoriesVerticals: ConnectorCategory[] = Object.values(ConnectorCategory); diff --git a/packages/shared/src/enum.ts b/packages/shared/src/connectors/enum.ts similarity index 56% rename from packages/shared/src/enum.ts rename to packages/shared/src/connectors/enum.ts index 9246584f0..b1a2ffc88 100644 --- a/packages/shared/src/enum.ts +++ b/packages/shared/src/connectors/enum.ts @@ -1,14 +1,4 @@ -export enum ProviderVertical { - CRM = 'crm', - HRIS = 'hris', - ATS = 'ats', - Accounting = 'accounting', - Ticketing = 'ticketing', - MarketingAutomation = 'marketingautomation', - FileStorage = 'filestorage', -} - -export enum CrmProviders { +export enum CrmConnectors { ZOHO = 'zoho', ZENDESK = 'zendesk', HUBSPOT = 'hubspot', @@ -16,7 +6,7 @@ export enum CrmProviders { ATTIO = 'attio' } -export enum TicketingProviders { +export enum TicketingConnectors { ZENDESK = 'zendesk', FRONT = 'front', GITHUB = 'github', @@ -26,7 +16,7 @@ export enum TicketingProviders { HUBSPOT = 'hubspot', } -export enum AccountingProviders { +export enum AccountingConnectors { PENNYLANE = 'pennylane', FRESHBOOKS = 'freshbooks', CLEARBOOKS = 'clearbooks', diff --git a/packages/shared/src/connectors/index.ts b/packages/shared/src/connectors/index.ts new file mode 100644 index 000000000..f7d873782 --- /dev/null +++ b/packages/shared/src/connectors/index.ts @@ -0,0 +1,7 @@ +export const CRM_PROVIDERS = ['zoho', 'zendesk', 'hubspot', 'pipedrive', 'attio']; +export const HRIS_PROVIDERS = ['']; +export const ATS_PROVIDERS = ['']; +export const ACCOUNTING_PROVIDERS = ['']; +export const TICKETING_PROVIDERS = ['zendesk', 'front', 'github', 'jira', 'gorgias', 'gitlab', 'hubspot']; +export const MARKETINGAUTOMATION_PROVIDERS = ['']; +export const FILESTORAGE_PROVIDERS = ['']; diff --git a/packages/shared/src/connectors/metadata.ts b/packages/shared/src/connectors/metadata.ts new file mode 100644 index 000000000..05b74af78 --- /dev/null +++ b/packages/shared/src/connectors/metadata.ts @@ -0,0 +1,2088 @@ +// If authBaseUrl or apiUrl both start with / it means a subdomain is likely needed +// If authBaseUrl is blank then it must be manually built in the client given the provider (meaning its not deterministic) + +import { AuthStrategy, ProvidersConfig } from '../types'; + +export const CONNECTORS_METADATA: ProvidersConfig = { + 'crm': { + 'hubspot': { + scopes: 'crm.objects.companies.read crm.objects.companies.write crm.objects.contacts.read crm.objects.contacts.write crm.objects.deals.read crm.objects.deals.write', + urls: { + docsUrl: 'https://developers.hubspot.com/docs/api/crm/understanding-the-crm', + authBaseUrl: 'https://app-eu1.hubspot.com/oauth/authorize', + apiUrl: 'https://api.hubapi.com/crm/v3', + customPropertiesUrl: 'https://api.hubapi.com/properties/v1/contacts/properties', + }, + logoPath: 'https://assets-global.website-files.com/6421a177cdeeaf3c6791b745/64d61202dd99e63d40d446f6_hubspot%20logo.png', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + authStrategy: AuthStrategy.oauth2 + }, + 'attio': { + scopes: 'record_permission:read', + urls: { + docsUrl: 'https://developers.attio.com/reference', + authBaseUrl: 'https://app.attio.com/authorize', + apiUrl: 'https://api.attio.com/v2', + customPropertiesUrl: '/docs/standard-objects-people', + }, + logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSJWZsShi0G6mZ451MngEvQrmJ2JIGH-AF8JyFU-q-n3w&s', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + authStrategy: AuthStrategy.oauth2 + }, + 'zoho': { + scopes: 'ZohoCRM.modules.ALL', + urls: { + docsUrl: 'https://www.zoho.com/crm/developer/docs/api/v5/', + authBaseUrl: '/oauth/v2/auth', + apiUrl: '/crm/v3', + customPropertiesUrl: '/settings/fields?module=Contact', + }, + logoPath: 'https://assets-global.website-files.com/64f68d43d25e5962af5f82dd/64f68d43d25e5962af5f9812_64ad8bbe47c78358489b29fc_645e3ccf636a8d659f320e25_Group%25252012.png', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + authStrategy: AuthStrategy.oauth2 + }, + 'pipedrive': { + urls: { + docsUrl: 'https://developers.pipedrive.com/docs/api/v1', + authBaseUrl: 'https://oauth.pipedrive.com/oauth/authorize', + apiUrl: 'https://api.pipedrive.com/v1', + customPropertiesUrl: '/v1/personFields', + }, + logoPath: 'https://asset.brandfetch.io/idZG_U1qqs/ideqSFbb2E.jpeg', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + authStrategy: AuthStrategy.oauth2 + }, + // todo + 'freshsales': { + scopes: '', + urls: { + docsUrl: '', + authBaseUrl: '', + apiUrl: '', + }, + logoPath: 'https://play-lh.googleusercontent.com/Mwgb5c2sVHGHoDlthAYPnMGekEOzsvMR5zotxskrl0erKTW-xpZbuIXn7AEIqvrRHQ', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + authStrategy: AuthStrategy.oauth2 + }, + 'zendesk': { + scopes: 'read write', + urls: { + docsUrl: 'https://developer.zendesk.com/api-reference/sales-crm/introduction/', + authBaseUrl: 'https://api.getbase.com/oauth2/authorize', + apiUrl: 'https://api.getbase.com/v2', + customPropertiesUrl: '/contact/custom_fields', + }, + logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRNKVceZGVM7PbARp_2bjdOICUxlpS5B29UYlurvh6Z2Q&s', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + authStrategy: AuthStrategy.oauth2, + }, + 'accelo': { + scopes: '', + urls: { + docsUrl: 'https://api.accelo.com/docs/#introduction', + authBaseUrl: '/oauth2/v0/authorize', + apiUrl: '/api/v0', + }, + logoPath: 'https://play-lh.googleusercontent.com/j63K2u8ZXukgPs8QPgyXfyoxuNBl_ST7gLx5DEFeczCTtM9e5JNpDjjBy32qLxFS7p0', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + authStrategy: AuthStrategy.oauth2 + }, + 'active_campaign': { + scopes: '', + urls: { + docsUrl: 'https://developers.activecampaign.com/reference/overview', + apiUrl: '/api/3', + }, + logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSymrBOaXpQab_5RPRZfiOXU7h9dfsduGZeCaZZw59xJA&s', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + authStrategy: AuthStrategy.api_key + }, + 'affinity': { + scopes: '', + urls: { + docsUrl: 'https://api-docs.affinity.co/#getting-started', + apiUrl: 'https://api.affinity.co', + }, + logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTMRfcwBA9Jn9z9dJQgY3f_H-bBeUzl-jRHNOm8xrmwtA&s', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + authStrategy: AuthStrategy.api_key + }, + 'capsule': { + scopes: '', + urls: { + docsUrl: 'https://developer.capsulecrm.com/', + authBaseUrl: 'https://api.capsulecrm.com/oauth/authorise', + apiUrl: 'https://api.capsulecrm.com/api/v2', + }, + logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSjS3qFlJJbQ802nGEV9w2GEgmnAIgJj6JJxe14cH6Wuw&s', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + authStrategy: AuthStrategy.oauth2 + }, + 'close': { + urls: { + docsUrl: 'https://developer.close.com/', + authBaseUrl: 'https://app.close.com/oauth2/authorize', + apiUrl: 'https://api.close.com/api/v1', + }, + logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTEH77yPBUkStmoc1ZtgJS4XeBmQiaq_Q1vgF5oerOGbg&s', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + authStrategy: AuthStrategy.oauth2 + }, + 'copper': { + scopes: '', + urls: { + docsUrl: 'https://developer.copper.com/index.html', + authBaseUrl: 'https://app.copper.com/oauth/authorize', + apiUrl: 'https://api.copper.com/developer_api/v1', + }, + logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRVa1YDciibzviRJxGovqH4gNgPxpZUAHEz36Bwnj54uQ&s', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + authStrategy: AuthStrategy.oauth2 + }, + 'insightly': { + scopes: '', + urls: { + docsUrl: 'https://api.insightly.com/v3.1/Help#!/Overview/Introduction', + apiUrl: '/v3.1', + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + authStrategy: AuthStrategy.api_key, + }, + 'keap': { + scopes: '', + urls: { + docsUrl: 'https://developer.infusionsoft.com/docs/restv2/', + authBaseUrl: 'https://accounts.infusionsoft.com/app/oauth/authorize', + apiUrl: 'https://api.infusionsoft.com/crm/rest/v2', + }, + logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRPYsWSMe9KVWgCIQ8fw-vBOnfTlZaSS6p_43ZhEIx51A&s', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + authStrategy: AuthStrategy.oauth2 + }, + // todo + 'microsoft_dynamics_sales': { + scopes: '', + urls: { + docsUrl: '', + authBaseUrl: '', + apiUrl: '', + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + // todo + 'nutshell': { + scopes: '', + urls: { + docsUrl: 'https://developers.nutshell.com/', + apiUrl: '/api/v1/json', + }, + logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRbCONyN9DCKfd4E8pzIdItl5VqPTEErpoEn9vHCgblRg&s', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + authStrategy: AuthStrategy.basic + }, + // todo + 'pipeliner': { + scopes: '', + urls: { + docsUrl: 'https://pipeliner.stoplight.io/docs/api-docs', + apiUrl: '', + }, + logoPath: 'https://play-lh.googleusercontent.com/rK9Qv_w9C8Py_aLZdQQDobNdHWSG8KL4dj3cBBQLcimVu-ctxwujA4VE442lIpZ65AE', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + authStrategy: AuthStrategy.api_key + }, + 'salesflare': { + scopes: '', + urls: { + docsUrl: 'https://api.salesflare.com/docs#section/Introduction/Getting-Started', + apiUrl: 'https://api.salesflare.com', + }, + logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTesqSVCSaCDrjedsKbepr14iJPySzUwrh7Fg9MhgKh9w&s', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + authStrategy: AuthStrategy.api_key + }, + // todo + 'salesforce': { + scopes: '', + urls: { + docsUrl: '', + authBaseUrl: '', + apiUrl: '', + }, + logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTgL4FJb-GptGfxDDkWbIX2CjIM77t5q-d7eCFY6sGsHA&s', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + authStrategy: AuthStrategy.oauth2 + }, + // todo + 'sugarcrm': { + scopes: '', + urls: { + docsUrl: '', + authBaseUrl: '', + apiUrl: '', + }, + logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQftNERc1ImBHm8MXXuWdhQiFYwW-dXNcogRL1UV8JyHFQGY2BbsbpwKvERwKRB39RH6zw&usqp=CAU', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + authStrategy: AuthStrategy.oauth2 + }, + 'teamleader': { + urls: { + docsUrl: 'https://developer.teamleader.eu/#/introduction/ap-what?', + authBaseUrl: 'https://focus.teamleader.eu/oauth2/authorize', + apiUrl: 'https://api.focus.teamleader.eu', + }, + logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTE99rDOwXdRYGET0oeSCqK2kB02slJxZtTeBC79pb8IQ&s', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + authStrategy: AuthStrategy.oauth2 + }, + 'teamwork': { + urls: { + docsUrl: 'https://apidocs.teamwork.com/guides/teamwork/getting-started-with-the-teamwork-com-api', + authBaseUrl: 'https://www.teamwork.com/launchpad/login', + apiUrl: '', // on purpose blank => everything is contained inside the accountUrl(subdomain) + }, + logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQr6gYDMNagMEicBb4dhKz4BC1fQs72In45QF7Ls6-moA&s', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + authStrategy: AuthStrategy.oauth2 + }, + // todo + 'vtiger': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '', + }, + logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRcUYrYD8lnaFaDN93vwjHhksKJUG3rqlb1TCFC__oPBw&s', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + authStrategy: AuthStrategy.basic + }, + // todo + 'twenty': { + scopes: '', + urls: { + docsUrl: '', + authBaseUrl: '', + apiUrl: '', + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + }, + 'ticketing': { + 'front': { + urls: { + docsUrl: 'https://dev.frontapp.com/docs/welcome', + authBaseUrl: 'https://app.frontapp.com/oauth/authorize', + apiUrl: 'https://api2.frontapp.com', + }, + logoPath: 'https://i.pinimg.com/originals/43/a2/43/43a24316bd773798c7638ad98521eb81.png', + description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', + authStrategy: AuthStrategy.oauth2 + }, + 'zendesk': { + scopes: 'read write', + urls: { + docsUrl: 'https://developer.zendesk.com/api-reference/sales-crm/introduction/', + authBaseUrl: '/oauth/authorizations/new', + apiUrl: '/api/v2', + }, + logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRNKVceZGVM7PbARp_2bjdOICUxlpS5B29UYlurvh6Z2Q&s', + description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', + authStrategy: AuthStrategy.oauth2 + }, + 'gorgias': { + scopes: 'write:all openid email profile offline', + urls: { + docsUrl: 'https://developers.gorgias.com/reference/introduction', + apiUrl: '/api', + authBaseUrl: `/connections/gorgias/oauth/install`, + }, + logoPath: 'https://x5h8w2v3.rocketcdn.me/wp-content/uploads/2020/09/FS-AFFI-00660Gorgias.png', + description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', + authStrategy: AuthStrategy.oauth2 + }, + 'jira': { + scopes: 'read:jira-work manage:jira-project manage:jira-data-provider manage:jira-webhook write:jira-work manage:jira-configuration read:jira-user offline_access', + urls: { + docsUrl: '', + apiUrl: '/rest/api/3', + authBaseUrl: 'https://auth.atlassian.com/authorize', + }, + logoPath: 'https://logowik.com/content/uploads/images/jira3124.jpg', + description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', + authStrategy: AuthStrategy.oauth2 + }, + // todo + 'jira_service_mgmt': { + scopes: 'read:servicedesk-request manage:servicedesk-customer read:servicemanagement-insight-objects write:servicedesk-request offline_access', + urls: { + docsUrl: '', + apiUrl: '', + authBaseUrl: 'https://auth.atlassian.com/authorize' + }, + logoPath: '', + description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', + active: false, + authStrategy: AuthStrategy.oauth2 + }, + 'linear': { + scopes: 'read,write', + urls: { + docsUrl: 'https://developers.linear.app/docs', + apiUrl: 'https://api.linear.app/graphql', + authBaseUrl: 'https://linear.app/oauth/authorize', + }, + logoPath: '', + description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', + active: false, + authStrategy: AuthStrategy.oauth2 + }, + 'gitlab': { + scopes: 'api read_api read_user create_runner k8s_proxy read_repository write_repository sudo admin_mode read_service_ping openid profile email', + urls: { + docsUrl: 'https://docs.gitlab.com/ee/api/rest/#', + apiUrl: 'https://gitlab.com/api/v4', + authBaseUrl: 'https://gitlab.com/oauth/authorize', + }, + logoPath: 'https://asset.brandfetch.io/idw382nG0m/idVn6myaqy.png', + description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', + active: true, + authStrategy: AuthStrategy.oauth2 + }, + 'clickup': { + urls: { + docsUrl: 'https://clickup.com/api/', + apiUrl: 'https://api.clickup.com/v2', + authBaseUrl: 'https://app.clickup.com/api', + }, + logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRewJj9y5yKzSCf-qGgjmdLagEhxfnlZ7TUsvukbfZaIg&s', + description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', + active: false, + authStrategy: AuthStrategy.oauth2 + }, + 'github': { + scopes: '', + urls: { + docsUrl: 'https://docs.github.com/fr/rest', + apiUrl: 'https://api.github.com', + authBaseUrl: 'https://github.com/login/oauth/authorize', + }, + logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', + description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', + active: false, + authStrategy: AuthStrategy.oauth2 + }, + 'aha': { + urls: { + docsUrl: 'https://www.aha.io/api', + apiUrl: '/api/v1', + authBaseUrl: '/oauth/authorize', + }, + logoPath: 'https://www.aha.io/aha-logo-2x.png', + description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', + active: false, + authStrategy: AuthStrategy.oauth2 + }, + 'asana': { + scopes: '', + urls: { + docsUrl: 'https://developers.asana.com/docs/overview', + apiUrl: 'https://app.asana.com/api/1.0', + authBaseUrl: 'https://app.asana.com/-/oauth_authorize', + }, + logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', + description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', + active: false, + authStrategy: AuthStrategy.oauth2 + }, + // todo + 'azure': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '', + authBaseUrl: '', + }, + logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', + description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', + active: false, + }, + // todo + 'basecamp': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '', + authBaseUrl: '', + }, logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', + description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', + active: false, + }, + // todo + 'bitbucket': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '', + authBaseUrl: '', + }, logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', + description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', + active: false, + }, + 'dixa': { + scopes: '', + urls: { + docsUrl: 'https://docs.dixa.io/docs/', + apiUrl: 'https://dev.dixa.io', + }, + logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', + description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', + active: false, + authStrategy: AuthStrategy.api_key + }, + // todo + 'freshdesk': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '', + authBaseUrl: '', + }, + logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', + description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', + active: false, + }, + // todo + 'freshservice': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '', + authBaseUrl: '', + }, + logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', + description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', + active: false, + }, + 'gladly': { + scopes: '', + urls: { + docsUrl: 'https://developer.gladly.com/rest/', + apiUrl: '/api/v1', + }, + logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', + description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', + active: false, + authStrategy: AuthStrategy.basic + }, + // todo + 'height': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '', + authBaseUrl: '', + }, + logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', + description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', + active: false, + }, + 'help_scout': { + scopes: '', + urls: { + docsUrl: 'https://developer.helpscout.com/docs-api/', + apiUrl: 'https://docsapi.helpscout.net/v1', + }, + logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', + description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', + active: false, + authStrategy: AuthStrategy.api_key + }, + 'hive': { + scopes: '', + urls: { + docsUrl: 'https://developers.hive.com/reference/introduction', + apiUrl: 'https://app.hive.com/api/v1', + }, + logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', + description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', + active: false, + authStrategy: AuthStrategy.api_key + }, + // todo + 'intercom': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '', + }, + logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', + description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', + active: false, + }, + // todo + 'ironclad': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '', + }, + logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', + description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', + active: false, + authStrategy: AuthStrategy.api_key + }, + 'kustomer': { + scopes: '', + urls: { + docsUrl: 'https://developer.kustomer.com/kustomer-api-docs/reference/introduction', + apiUrl: 'https://api.kustomerapp.com', + }, + logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', + description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', + active: false, + authStrategy: AuthStrategy.api_key + }, + // todo + 'pivotal_tracker': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '', + }, + logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', + description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', + active: false, + }, + // todo + 'rally': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '', + }, + logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', + description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', + active: false, + }, + 'reamaze': { + scopes: '', + urls: { + docsUrl: 'https://www.reamaze.com/api', + apiUrl: '/api/v1', + }, + logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', + description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', + active: false, + authStrategy: AuthStrategy.api_key + }, + // todo + 'salesforce': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '', + }, + logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', + description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', + active: false, + }, + // todo + 'servicenow': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '', + }, + logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', + description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', + active: false, + }, + 'shortcut': { + scopes: '', + urls: { + docsUrl: 'https://developer.shortcut.com/api/rest/v3', + apiUrl: 'https://api.app.shortcut.com', + }, + logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', + description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', + active: false, + authStrategy: AuthStrategy.api_key + }, + // todo + 'spotdraft': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '', + }, + logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', + description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', + active: false, + }, + // todo + 'teamwork': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '', + }, + logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', + description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', + active: false, + }, + // todo + 'trello': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: 'https://api.app.shortcut.com', + }, + logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', + description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', + active: false, + }, + 'wrike': { + scopes: '', + urls: { + docsUrl: 'https://developers.wrike.com/overview/', + apiUrl: '/api/v4', + authBaseUrl: 'https://login.wrike.com/oauth2/authorize/v4', + }, + logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', + description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', + active: false, + authStrategy: AuthStrategy.oauth2 + }, + 'zoho_bugtracker': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '', + }, + logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', + description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', + active: false, + }, + 'zoho_desk': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '', + }, + logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', + description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', + active: false, + }, + }, + 'accounting': { + 'pennylane': { + scopes: '', + urls: { + docsUrl: 'https://pennylane.readme.io/docs/getting-started', + apiUrl: 'https://app.pennylane.com/api/external/v1', + authBaseUrl: 'https://app.pennylane.com/oauth/authorize', + }, + logoPath: 'https://cdn-images-1.medium.com/max/1200/1*wk7CNGik_1Szbt7s1fNZxA.png', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + authStrategy: AuthStrategy.oauth2 + }, + 'freshbooks': { + scopes: '', + urls: { + docsUrl: 'https://www.freshbooks.com/api/start', + apiUrl: 'https://api.freshbooks.com', + authBaseUrl: 'https://auth.freshbooks.com/oauth/authorize', + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + authStrategy: AuthStrategy.oauth2 + }, + // todo + 'clearbooks': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '', + authBaseUrl: '', + }, + logoPath: 'https://s3-eu-west-1.amazonaws.com/clearbooks-marketing/media-centre/MediaCentre/clear-books/CMYK/icon/clear-books-icon-cmyk.png', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'freeagent': { + urls: { + docsUrl: 'https://dev.freeagent.com/docs/quick_start', + apiUrl: 'https://api.freeagent.com/v2', + authBaseUrl: 'https://api.freeagent.com/v2/approve_app', + }, + logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQU-fob0b9pBNQdm80usnYa2yWdagm3eeBDH-870vSmfg&s', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + authStrategy: AuthStrategy.oauth2 + }, + 'sage': { + scopes: '', + urls: { + docsUrl: 'https://developer.sage.com/accounting/reference/', + apiUrl: 'https://api.accounting.sage.com/v3.1', + authBaseUrl: 'https://www.sageone.com/oauth2/auth/central?filter=apiv3.1', + }, + logoPath: 'https://upload.wikimedia.org/wikipedia/en/thumb/b/b7/Sage_Group_logo_2022.svg/2560px-Sage_Group_logo_2022.svg.png', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + authStrategy: AuthStrategy.oauth2 + }, + // todo + 'sage_intacct': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '', + authBaseUrl: '', + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + // todo + 'microsoft_dynamics': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '', + authBaseUrl: '', + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'moneybird': { + scopes: '', + urls: { + docsUrl: 'https://developer.moneybird.com/', + apiUrl: 'https://moneybird.com/api/v2', + authBaseUrl: 'https://moneybird.com/oauth/authorize', + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + authStrategy: AuthStrategy.oauth2 + }, + // todo + 'netsuite': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '', + authBaseUrl: '', + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'quickbooks': { + scopes: '', + urls: { + docsUrl: 'https://developer.intuit.com/app/developer/qbo/docs/develop', + apiUrl: 'https://quickbooks.api.intuit.com/v3', + authBaseUrl: 'https://appcenter.intuit.com/connect/oauth2', + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + authStrategy: AuthStrategy.oauth2 + }, + // todo + 'workday': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '', + authBaseUrl: '', + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'wave_financial': { + scopes: '', + urls: { + docsUrl: 'https://developer.waveapps.com/hc/en-us/articles/360019968212-API-Reference', + apiUrl: 'https://gql.waveapps.com/graphql/public', + authBaseUrl: 'https://api.waveapps.com/oauth2/authorize/', + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + authStrategy: AuthStrategy.oauth2 + }, + }, + 'marketingautomation': { + 'active_campaign': { + scopes: '', + urls: { + docsUrl: 'https://developers.activecampaign.com/reference/overview', + apiUrl: '/api/3' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + authStrategy: AuthStrategy.api_key + }, + // todo + 'customerio': { + scopes: '', + urls: { + docsUrl: 'https://customer.io/docs/api/track/', + apiUrl: 'https://track.customer.io/api/' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'getresponse': { + urls: { + authBaseUrl: 'https://app.getresponse.com/oauth2_authorize.html', + docsUrl: 'https://apidocs.getresponse.com/v3', + apiUrl: 'https://api.getresponse.com/v3' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + authStrategy: AuthStrategy.oauth2 + }, + // todo + 'hubspot_marketing_hub': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + // todo + 'keap': { + scopes: '', + urls: { + authBaseUrl: 'https://accounts.infusionsoft.com/app/oauth/authorize', + docsUrl: 'https://developer.infusionsoft.com/docs/rest/', + apiUrl: 'https://api.infusionsoft.com/crm/rest/v1/account/profile' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + authStrategy: AuthStrategy.oauth2 + }, + 'klaviyo': { + scopes: '', + urls: { + docsUrl: 'https://developers.klaviyo.com/en/reference/api_overview', + apiUrl: 'https://a.klaviyo.com/api' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + authStrategy: AuthStrategy.api_key + }, + 'mailchimp': { + scopes: '', + urls: { + authBaseUrl: 'https://login.mailchimp.com/oauth2/authorize', + docsUrl: 'https://mailchimp.com/developer/marketing/api/', + apiUrl: '' // todo + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + authStrategy: AuthStrategy.oauth2 + }, + 'messagebird': { + scopes: '', + urls: { + docsUrl: 'https://developers.messagebird.com/api/', + apiUrl: 'https://rest.messagebird.com' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + authStrategy: AuthStrategy.api_key + }, + 'podium': { + scopes: '', + urls: { + authBaseUrl: 'https://api.podium.com/oauth/authorize', + docsUrl: 'https://docs.podium.com/reference/introduction', + apiUrl: 'https://api.podium.com/v4' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + authStrategy: AuthStrategy.oauth2 + }, + 'sendgrid': { + scopes: '', + urls: { + docsUrl: 'https://docs.sendgrid.com/for-developers/sending-email/api-getting-started', + apiUrl: 'https://api.sendgrid.com/v3' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + authStrategy: AuthStrategy.api_key + }, + 'brevo': { + scopes: '', + urls: { + docsUrl: 'https://developers.brevo.com/docs/getting-started', + apiUrl: 'https://api.brevo.com/v3' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + authStrategy: AuthStrategy.api_key + }, + }, + // todo + 'ats': { + // todo + 'applicantstack': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'ashby': { + scopes: '', + urls: { + docsUrl: 'https://developers.ashbyhq.com', + apiUrl: 'https://api.ashbyhq.com' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + authStrategy: AuthStrategy.api_key + }, + // todo + 'bamboohr': { + scopes: '', + urls: { + docsUrl: 'https://documentation.bamboohr.com/docs/getting-started', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + authStrategy: AuthStrategy.api_key + }, + 'breezy': { + scopes: '', + urls: { + docsUrl: 'https://developer.breezy.hr/reference/overview', + apiUrl: 'https://api.breezy.hr/v3' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + authStrategy: AuthStrategy.api_key + }, + // todo + 'bullhorn': { + scopes: '', + urls: { + docsUrl: 'https://bullhorn.github.io/rest-api-docs/', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'cats': { + scopes: '', + urls: { + docsUrl: 'https://docs.catsone.com/api/v3/', + apiUrl: 'https://api.catsone.com/v3' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + authStrategy: AuthStrategy.api_key + }, + 'clayhr': { + scopes: '', + urls: { + docsUrl: 'https://clayhr.readme.io/', + apiUrl: '/rm/api/v3' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + authStrategy: AuthStrategy.api_key + }, + // todo + 'clockwork': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + // todo + 'comeet': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'cornerstone_talentlink': { + scopes: '', + urls: { + docsUrl: 'https://developer.lumesse-talenthub.com/rest-api-developers-guide/1.21.33/index.html?page=rest-api&subpage=introduction', + apiUrl: 'https://apiproxy.shared.lumessetalentlink.com/tlk/rest' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + authStrategy: AuthStrategy.api_key + }, + 'engage_ats': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'eploy': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'fountain': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'freshteam': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'greenhouse': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'greenhouse_job_boards': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'harbour_ats': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'homerun': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'hrcloud': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'icims': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'infinite_brassring': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'jazzhr': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'jobadder': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'jobscore': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'jobvite': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'lano': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'lever': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'occupop': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'oracle_fusion': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'oracle_taleo': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'personio_recruiting': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'pinpoint': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'polymer': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'recruiterflow': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'recruitive': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'sage_hr': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'sap_successfactors': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'smartrecruiters': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'talentlyft': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'talentreef': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'teamtailor': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'tellent': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'tribepad': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'ukg_pro_recruiting': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'workable': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'workday': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'zoho_recruit': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + }, + // TODO + 'hris': { + '7shifts': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'adp_workforce_now': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'alexishr': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'alliancehcm': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'altera_payroll': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'bamboohr': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'breathe': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'ceridian_dayforce': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'charlie': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'charthop': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'clayhr': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'cyberark': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'deel': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'employment_hero': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'factorial': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'freshteam': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'google_workspace': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'gusto': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'hibob': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'hrcloud': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'hrpartner': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'humaans': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'humi': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'insperity_premier': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'active_campaign': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'intellli_hr': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'iris_cascade': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'jumpcloud': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'justworks': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'kallidus': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'keka': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'kenjo': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'lano': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'lucca': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'microsoft_entra_id': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'namely': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'nmbrs': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'officient': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'okta': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'onelogin': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'oracle_hcm': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'oyster_hr': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'paycaptain': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'paychex': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'paycor': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'payfit': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'paylocity': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'people_hr': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'personio': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'pingone': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'proliant': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'remote': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'sage_hr': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'sap_successfactors': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'sesame': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'square_payroll': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'trinet': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'trinet_hr_platform': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'ukg_pro': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'ukg_pro_workforce': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'ukg_ready': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'workday': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + 'zoho_people': { + scopes: '', + urls: { + docsUrl: '', + apiUrl: '' + }, + logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', + description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', + active: false, + }, + } +}; diff --git a/packages/shared/src/envConfig.ts b/packages/shared/src/envConfig.ts index b2548abed..4ba8821d6 100644 --- a/packages/shared/src/envConfig.ts +++ b/packages/shared/src/envConfig.ts @@ -1,4 +1,5 @@ -import { AuthStrategy, providersConfig, SoftwareMode } from './utils'; +import { CONNECTORS_METADATA } from './connectors/metadata'; +import { AuthStrategy, SoftwareMode } from './types'; export type BasicAuthData = { USERNAME: string; @@ -77,19 +78,19 @@ export function extractAuthMode(type: string): AuthStrategy { export function needsSubdomain(provider: string, vertical: string): boolean { // Check if the vertical exists in the config - if (!providersConfig[vertical]) { - console.error(`Vertical ${vertical} not found in providersConfig.`); + if (!CONNECTORS_METADATA[vertical]) { + console.error(`Vertical ${vertical} not found in CONNECTORS_METADATA.`); return false; } // Check if the provider exists under the specified vertical - if (!providersConfig[vertical][provider]) { + if (!CONNECTORS_METADATA[vertical][provider]) { console.error(`Provider ${provider} not found under vertical ${vertical}.`); return false; } // Extract the provider's config - const providerConfig = providersConfig[vertical][provider]; + const providerConfig = CONNECTORS_METADATA[vertical][provider]; const authBaseUrlStartsWithSlash = providerConfig.urls.authBaseUrl!.substring(0,1) === '/'; const apiUrlStartsWithSlash = providerConfig.urls.apiUrl!.substring(0,1) === '/'; diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index 9e9b4437c..607718c2a 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -1,11 +1 @@ -// export public api from here -// for example: -// export * from './decorators'; export * from './test'; -// export * from './providers'; -// export * from './enum'; -// export * from './standardObjects'; -// export * from './webhookScopes'; -// export * from './authUrl'; -// export * from './utils'; -// export * from './envConfig'; diff --git a/packages/shared/src/providers.ts b/packages/shared/src/providers.ts deleted file mode 100644 index 5892f9b59..000000000 --- a/packages/shared/src/providers.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { ProviderVertical } from './enum'; - -export const categoriesVerticals = Object.values(ProviderVertical); - -export const CRM_PROVIDERS = ['zoho', 'zendesk', 'hubspot', 'pipedrive', 'attio']; - -export const HRIS_PROVIDERS = ['']; -export const ATS_PROVIDERS = ['']; -export const ACCOUNTING_PROVIDERS = ['']; -export const TICKETING_PROVIDERS = ['zendesk', 'front', 'github', 'jira', 'gorgias', 'gitlab', 'hubspot']; -export const MARKETINGAUTOMATION_PROVIDERS = ['']; -export const FILESTORAGE_PROVIDERS = ['']; - -/*export function getProviderVertical(providerName: string): ProviderVertical { - if (CRM_PROVIDERS.includes(providerName)) { - return ProviderVertical.CRM; - } - if (HRIS_PROVIDERS.includes(providerName)) { - return ProviderVertical.HRIS; - } - if (ATS_PROVIDERS.includes(providerName)) { - return ProviderVertical.ATS; - } - if (ACCOUNTING_PROVIDERS.includes(providerName)) { - return ProviderVertical.Accounting; - } - if (TICKETING_PROVIDERS.includes(providerName)) { - return ProviderVertical.Ticketing; - } - if (MARKETINGAUTOMATION_PROVIDERS.includes(providerName)) { - return ProviderVertical.MarketingAutomation; - } - if (FILESTORAGE_PROVIDERS.includes(providerName)) { - return ProviderVertical.FileStorage; - } - return undefined; -}*/ - -function mergeAllProviders(...arrays: string[][]): { vertical: string, value: string }[] { - const result: { vertical: string, value: string }[] = []; - arrays.forEach((arr, index) => { - const arrayName = Object.keys({ CRM_PROVIDERS, HRIS_PROVIDERS, ATS_PROVIDERS, ACCOUNTING_PROVIDERS, TICKETING_PROVIDERS, MARKETINGAUTOMATION_PROVIDERS, FILESTORAGE_PROVIDERS })[index]; - arr.forEach(item => { - if (item !== '') { - result.push({ vertical: arrayName.split('_')[0], value: item }); - } - }); - }); - return result; -} - -export const ALL_PROVIDERS: { vertical: string, value: string }[] = mergeAllProviders(CRM_PROVIDERS, HRIS_PROVIDERS, ATS_PROVIDERS, ACCOUNTING_PROVIDERS, TICKETING_PROVIDERS, MARKETINGAUTOMATION_PROVIDERS, FILESTORAGE_PROVIDERS) diff --git a/packages/shared/src/test.ts b/packages/shared/src/test.ts index 6f7c0c706..d8ec76f83 100644 --- a/packages/shared/src/test.ts +++ b/packages/shared/src/test.ts @@ -1,7 +1,10 @@ -export * from './providers'; -export * from './enum'; +export * from './connectors'; +export * from './connectors/enum'; export * from './standardObjects'; export * from './webhookScopes'; export * from './authUrl'; export * from './utils'; export * from './envConfig'; +export * from './connectors/metadata'; +export * from './types'; +export * from './categories'; diff --git a/packages/shared/src/types.ts b/packages/shared/src/types.ts new file mode 100644 index 000000000..1e830f7e2 --- /dev/null +++ b/packages/shared/src/types.ts @@ -0,0 +1,32 @@ +export enum AuthStrategy { + oauth2 = '0Auth2', + api_key = 'API Key', + basic = 'Basic Auth' +} + +export enum SoftwareMode { + cloud = 'CLOUD', +} + +export type ProviderConfig = { + scopes?: string; + logoPath: string; + description: string; + active?: boolean; + customPropertiesUrl?: string; + authStrategy?: AuthStrategy; + urls: { + docsUrl: string; + apiUrl: string; + authBaseUrl?: string; // url used to authorize an application on behalf of the user (only when authStrategy is oauth2) + customPropertiesUrl?: string; + } +}; + +export type VerticalConfig = { + [key: string]: ProviderConfig; +}; + +export type ProvidersConfig = { + [vertical: string]: VerticalConfig; +} diff --git a/packages/shared/src/utils.ts b/packages/shared/src/utils.ts index d192d886b..36acc21c4 100644 --- a/packages/shared/src/utils.ts +++ b/packages/shared/src/utils.ts @@ -1,2128 +1,20 @@ -import { categoriesVerticals } from './providers'; - -export enum AuthStrategy { - oauth2 = '0Auth2', - api_key = 'API Key', - basic = 'Basic Auth' -} - -export enum SoftwareMode { - cloud = 'CLOUD', -} - -export type ProviderConfig = { - scopes?: string; - logoPath: string; - description: string; - active?: boolean; - - customPropertiesUrl?: string; - authStrategy?: AuthStrategy; - urls: { - docsUrl: string; - apiUrl: string; - authBaseUrl?: string; // url used to authorize an application on behalf of the user (only when authStrategy is oauth2) - customPropertiesUrl?: string; +import { CONNECTORS_METADATA } from './connectors/metadata'; +import { ACCOUNTING_PROVIDERS, ATS_PROVIDERS, CRM_PROVIDERS, FILESTORAGE_PROVIDERS, HRIS_PROVIDERS, MARKETINGAUTOMATION_PROVIDERS, TICKETING_PROVIDERS } from './connectors'; +import { AuthStrategy, VerticalConfig } from './types'; +import { categoriesVerticals, ConnectorCategory } from './categories'; + +export const randomString = () => { + const charSet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + let result = ''; + for (let i = 0; i < length; i++) { + const randomIndex = Math.floor(Math.random() * charSet.length); + result += charSet[randomIndex]; } -}; - -type VerticalConfig = { - [key: string]: ProviderConfig; -}; - -export type ProvidersConfig = { - [vertical: string]: VerticalConfig; + return result; } -// If authBaseUrl or apiUrl both start with / it means a subdomain is likely needed -// If authBaseUrl is blank then it must be manually built in the client given the provider (meaning its not deterministic) - -export const providersConfig: ProvidersConfig = { - 'crm': { - 'hubspot': { - scopes: 'crm.objects.companies.read crm.objects.companies.write crm.objects.contacts.read crm.objects.contacts.write crm.objects.deals.read crm.objects.deals.write', - urls: { - docsUrl: 'https://developers.hubspot.com/docs/api/crm/understanding-the-crm', - authBaseUrl: 'https://app-eu1.hubspot.com/oauth/authorize', - apiUrl: 'https://api.hubapi.com/crm/v3', - customPropertiesUrl: 'https://api.hubapi.com/properties/v1/contacts/properties', - }, - logoPath: 'https://assets-global.website-files.com/6421a177cdeeaf3c6791b745/64d61202dd99e63d40d446f6_hubspot%20logo.png', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - authStrategy: AuthStrategy.oauth2 - }, - 'attio': { - scopes: 'record_permission:read', - urls: { - docsUrl: 'https://developers.attio.com/reference', - authBaseUrl: 'https://app.attio.com/authorize', - apiUrl: 'https://api.attio.com/v2', - customPropertiesUrl: '/docs/standard-objects-people', - }, - logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSJWZsShi0G6mZ451MngEvQrmJ2JIGH-AF8JyFU-q-n3w&s', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - authStrategy: AuthStrategy.oauth2 - }, - 'zoho': { - scopes: 'ZohoCRM.modules.ALL', - urls: { - docsUrl: 'https://www.zoho.com/crm/developer/docs/api/v5/', - authBaseUrl: '/oauth/v2/auth', - apiUrl: '/crm/v3', - customPropertiesUrl: '/settings/fields?module=Contact', - }, - logoPath: 'https://assets-global.website-files.com/64f68d43d25e5962af5f82dd/64f68d43d25e5962af5f9812_64ad8bbe47c78358489b29fc_645e3ccf636a8d659f320e25_Group%25252012.png', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - authStrategy: AuthStrategy.oauth2 - }, - 'pipedrive': { - urls: { - docsUrl: 'https://developers.pipedrive.com/docs/api/v1', - authBaseUrl: 'https://oauth.pipedrive.com/oauth/authorize', - apiUrl: 'https://api.pipedrive.com/v1', - customPropertiesUrl: '/v1/personFields', - }, - logoPath: 'https://asset.brandfetch.io/idZG_U1qqs/ideqSFbb2E.jpeg', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - authStrategy: AuthStrategy.oauth2 - }, - // todo - 'freshsales': { - scopes: '', - urls: { - docsUrl: '', - authBaseUrl: '', - apiUrl: '', - }, - logoPath: 'https://play-lh.googleusercontent.com/Mwgb5c2sVHGHoDlthAYPnMGekEOzsvMR5zotxskrl0erKTW-xpZbuIXn7AEIqvrRHQ', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - authStrategy: AuthStrategy.oauth2 - }, - 'zendesk': { - scopes: 'read write', - urls: { - docsUrl: 'https://developer.zendesk.com/api-reference/sales-crm/introduction/', - authBaseUrl: 'https://api.getbase.com/oauth2/authorize', - apiUrl: 'https://api.getbase.com/v2', - customPropertiesUrl: '/contact/custom_fields', - }, - logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRNKVceZGVM7PbARp_2bjdOICUxlpS5B29UYlurvh6Z2Q&s', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - authStrategy: AuthStrategy.oauth2, - }, - 'accelo': { - scopes: '', - urls: { - docsUrl: 'https://api.accelo.com/docs/#introduction', - authBaseUrl: '/oauth2/v0/authorize', - apiUrl: '/api/v0', - }, - logoPath: 'https://play-lh.googleusercontent.com/j63K2u8ZXukgPs8QPgyXfyoxuNBl_ST7gLx5DEFeczCTtM9e5JNpDjjBy32qLxFS7p0', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - authStrategy: AuthStrategy.oauth2 - }, - 'active_campaign': { - scopes: '', - urls: { - docsUrl: 'https://developers.activecampaign.com/reference/overview', - apiUrl: '/api/3', - }, - logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSymrBOaXpQab_5RPRZfiOXU7h9dfsduGZeCaZZw59xJA&s', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - authStrategy: AuthStrategy.api_key - }, - 'affinity': { - scopes: '', - urls: { - docsUrl: 'https://api-docs.affinity.co/#getting-started', - apiUrl: 'https://api.affinity.co', - }, - logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTMRfcwBA9Jn9z9dJQgY3f_H-bBeUzl-jRHNOm8xrmwtA&s', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - authStrategy: AuthStrategy.api_key - }, - 'capsule': { - scopes: '', - urls: { - docsUrl: 'https://developer.capsulecrm.com/', - authBaseUrl: 'https://api.capsulecrm.com/oauth/authorise', - apiUrl: 'https://api.capsulecrm.com/api/v2', - }, - logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSjS3qFlJJbQ802nGEV9w2GEgmnAIgJj6JJxe14cH6Wuw&s', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - authStrategy: AuthStrategy.oauth2 - }, - 'close': { - urls: { - docsUrl: 'https://developer.close.com/', - authBaseUrl: 'https://app.close.com/oauth2/authorize', - apiUrl: 'https://api.close.com/api/v1', - }, - logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTEH77yPBUkStmoc1ZtgJS4XeBmQiaq_Q1vgF5oerOGbg&s', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - authStrategy: AuthStrategy.oauth2 - }, - 'copper': { - scopes: '', - urls: { - docsUrl: 'https://developer.copper.com/index.html', - authBaseUrl: 'https://app.copper.com/oauth/authorize', - apiUrl: 'https://api.copper.com/developer_api/v1', - }, - logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRVa1YDciibzviRJxGovqH4gNgPxpZUAHEz36Bwnj54uQ&s', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - authStrategy: AuthStrategy.oauth2 - }, - 'insightly': { - scopes: '', - urls: { - docsUrl: 'https://api.insightly.com/v3.1/Help#!/Overview/Introduction', - apiUrl: '/v3.1', - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - authStrategy: AuthStrategy.api_key, - }, - 'keap': { - scopes: '', - urls: { - docsUrl: 'https://developer.infusionsoft.com/docs/restv2/', - authBaseUrl: 'https://accounts.infusionsoft.com/app/oauth/authorize', - apiUrl: 'https://api.infusionsoft.com/crm/rest/v2', - }, - logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRPYsWSMe9KVWgCIQ8fw-vBOnfTlZaSS6p_43ZhEIx51A&s', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - authStrategy: AuthStrategy.oauth2 - }, - // todo - 'microsoft_dynamics_sales': { - scopes: '', - urls: { - docsUrl: '', - authBaseUrl: '', - apiUrl: '', - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - // todo - 'nutshell': { - scopes: '', - urls: { - docsUrl: 'https://developers.nutshell.com/', - apiUrl: '/api/v1/json', - }, - logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRbCONyN9DCKfd4E8pzIdItl5VqPTEErpoEn9vHCgblRg&s', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - authStrategy: AuthStrategy.basic - }, - // todo - 'pipeliner': { - scopes: '', - urls: { - docsUrl: 'https://pipeliner.stoplight.io/docs/api-docs', - apiUrl: '', - }, - logoPath: 'https://play-lh.googleusercontent.com/rK9Qv_w9C8Py_aLZdQQDobNdHWSG8KL4dj3cBBQLcimVu-ctxwujA4VE442lIpZ65AE', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - authStrategy: AuthStrategy.api_key - }, - 'salesflare': { - scopes: '', - urls: { - docsUrl: 'https://api.salesflare.com/docs#section/Introduction/Getting-Started', - apiUrl: 'https://api.salesflare.com', - }, - logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTesqSVCSaCDrjedsKbepr14iJPySzUwrh7Fg9MhgKh9w&s', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - authStrategy: AuthStrategy.api_key - }, - // todo - 'salesforce': { - scopes: '', - urls: { - docsUrl: '', - authBaseUrl: '', - apiUrl: '', - }, - logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTgL4FJb-GptGfxDDkWbIX2CjIM77t5q-d7eCFY6sGsHA&s', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - authStrategy: AuthStrategy.oauth2 - }, - // todo - 'sugarcrm': { - scopes: '', - urls: { - docsUrl: '', - authBaseUrl: '', - apiUrl: '', - }, - logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQftNERc1ImBHm8MXXuWdhQiFYwW-dXNcogRL1UV8JyHFQGY2BbsbpwKvERwKRB39RH6zw&usqp=CAU', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - authStrategy: AuthStrategy.oauth2 - }, - 'teamleader': { - urls: { - docsUrl: 'https://developer.teamleader.eu/#/introduction/ap-what?', - authBaseUrl: 'https://focus.teamleader.eu/oauth2/authorize', - apiUrl: 'https://api.focus.teamleader.eu', - }, - logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTE99rDOwXdRYGET0oeSCqK2kB02slJxZtTeBC79pb8IQ&s', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - authStrategy: AuthStrategy.oauth2 - }, - 'teamwork': { - urls: { - docsUrl: 'https://apidocs.teamwork.com/guides/teamwork/getting-started-with-the-teamwork-com-api', - authBaseUrl: 'https://www.teamwork.com/launchpad/login', - apiUrl: '', // on purpose blank => everything is contained inside the accountUrl(subdomain) - }, - logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQr6gYDMNagMEicBb4dhKz4BC1fQs72In45QF7Ls6-moA&s', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - authStrategy: AuthStrategy.oauth2 - }, - // todo - 'vtiger': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '', - }, - logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRcUYrYD8lnaFaDN93vwjHhksKJUG3rqlb1TCFC__oPBw&s', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - authStrategy: AuthStrategy.basic - }, - // todo - 'twenty': { - scopes: '', - urls: { - docsUrl: '', - authBaseUrl: '', - apiUrl: '', - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - }, - 'ticketing': { - 'front': { - urls: { - docsUrl: 'https://dev.frontapp.com/docs/welcome', - authBaseUrl: 'https://app.frontapp.com/oauth/authorize', - apiUrl: 'https://api2.frontapp.com', - }, - logoPath: 'https://i.pinimg.com/originals/43/a2/43/43a24316bd773798c7638ad98521eb81.png', - description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', - authStrategy: AuthStrategy.oauth2 - }, - 'zendesk': { - scopes: 'read write', - urls: { - docsUrl: 'https://developer.zendesk.com/api-reference/sales-crm/introduction/', - authBaseUrl: '/oauth/authorizations/new', - apiUrl: '/api/v2', - }, - logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRNKVceZGVM7PbARp_2bjdOICUxlpS5B29UYlurvh6Z2Q&s', - description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', - authStrategy: AuthStrategy.oauth2 - }, - 'gorgias': { - scopes: 'write:all openid email profile offline', - urls: { - docsUrl: 'https://developers.gorgias.com/reference/introduction', - apiUrl: '/api', - authBaseUrl: `/connections/gorgias/oauth/install`, - }, - logoPath: 'https://x5h8w2v3.rocketcdn.me/wp-content/uploads/2020/09/FS-AFFI-00660Gorgias.png', - description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', - authStrategy: AuthStrategy.oauth2 - }, - 'jira': { - scopes: 'read:jira-work manage:jira-project manage:jira-data-provider manage:jira-webhook write:jira-work manage:jira-configuration read:jira-user offline_access', - urls: { - docsUrl: '', - apiUrl: '/rest/api/3', - authBaseUrl: 'https://auth.atlassian.com/authorize', - }, - logoPath: 'https://logowik.com/content/uploads/images/jira3124.jpg', - description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', - authStrategy: AuthStrategy.oauth2 - }, - // todo - 'jira_service_mgmt': { - scopes: 'read:servicedesk-request manage:servicedesk-customer read:servicemanagement-insight-objects write:servicedesk-request offline_access', - urls: { - docsUrl: '', - apiUrl: '', - authBaseUrl: 'https://auth.atlassian.com/authorize' - }, - logoPath: '', - description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', - active: false, - authStrategy: AuthStrategy.oauth2 - }, - 'linear': { - scopes: 'read,write', - urls: { - docsUrl: 'https://developers.linear.app/docs', - apiUrl: 'https://api.linear.app/graphql', - authBaseUrl: 'https://linear.app/oauth/authorize', - }, - logoPath: '', - description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', - active: false, - authStrategy: AuthStrategy.oauth2 - }, - 'gitlab': { - scopes: 'api read_api read_user create_runner k8s_proxy read_repository write_repository sudo admin_mode read_service_ping openid profile email', - urls: { - docsUrl: 'https://docs.gitlab.com/ee/api/rest/#', - apiUrl: 'https://gitlab.com/api/v4', - authBaseUrl: 'https://gitlab.com/oauth/authorize', - }, - logoPath: 'https://asset.brandfetch.io/idw382nG0m/idVn6myaqy.png', - description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', - active: true, - authStrategy: AuthStrategy.oauth2 - }, - 'clickup': { - urls: { - docsUrl: 'https://clickup.com/api/', - apiUrl: 'https://api.clickup.com/v2', - authBaseUrl: 'https://app.clickup.com/api', - }, - logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRewJj9y5yKzSCf-qGgjmdLagEhxfnlZ7TUsvukbfZaIg&s', - description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', - active: false, - authStrategy: AuthStrategy.oauth2 - }, - 'github': { - scopes: '', - urls: { - docsUrl: 'https://docs.github.com/fr/rest', - apiUrl: 'https://api.github.com', - authBaseUrl: 'https://github.com/login/oauth/authorize', - }, - logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', - description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', - active: false, - authStrategy: AuthStrategy.oauth2 - }, - 'aha': { - urls: { - docsUrl: 'https://www.aha.io/api', - apiUrl: '/api/v1', - authBaseUrl: '/oauth/authorize', - }, - logoPath: 'https://www.aha.io/aha-logo-2x.png', - description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', - active: false, - authStrategy: AuthStrategy.oauth2 - }, - 'asana': { - scopes: '', - urls: { - docsUrl: 'https://developers.asana.com/docs/overview', - apiUrl: 'https://app.asana.com/api/1.0', - authBaseUrl: 'https://app.asana.com/-/oauth_authorize', - }, - logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', - description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', - active: false, - authStrategy: AuthStrategy.oauth2 - }, - // todo - 'azure': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '', - authBaseUrl: '', - }, - logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', - description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', - active: false, - }, - // todo - 'basecamp': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '', - authBaseUrl: '', - }, logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', - description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', - active: false, - }, - // todo - 'bitbucket': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '', - authBaseUrl: '', - }, logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', - description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', - active: false, - }, - 'dixa': { - scopes: '', - urls: { - docsUrl: 'https://docs.dixa.io/docs/', - apiUrl: 'https://dev.dixa.io', - }, - logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', - description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', - active: false, - authStrategy: AuthStrategy.api_key - }, - // todo - 'freshdesk': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '', - authBaseUrl: '', - }, - logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', - description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', - active: false, - }, - // todo - 'freshservice': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '', - authBaseUrl: '', - }, - logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', - description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', - active: false, - }, - 'gladly': { - scopes: '', - urls: { - docsUrl: 'https://developer.gladly.com/rest/', - apiUrl: '/api/v1', - }, - logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', - description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', - active: false, - authStrategy: AuthStrategy.basic - }, - // todo - 'height': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '', - authBaseUrl: '', - }, - logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', - description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', - active: false, - }, - 'help_scout': { - scopes: '', - urls: { - docsUrl: 'https://developer.helpscout.com/docs-api/', - apiUrl: 'https://docsapi.helpscout.net/v1', - }, - logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', - description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', - active: false, - authStrategy: AuthStrategy.api_key - }, - 'hive': { - scopes: '', - urls: { - docsUrl: 'https://developers.hive.com/reference/introduction', - apiUrl: 'https://app.hive.com/api/v1', - }, - logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', - description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', - active: false, - authStrategy: AuthStrategy.api_key - }, - // todo - 'intercom': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '', - }, - logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', - description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', - active: false, - }, - // todo - 'ironclad': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '', - }, - logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', - description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', - active: false, - authStrategy: AuthStrategy.api_key - }, - 'kustomer': { - scopes: '', - urls: { - docsUrl: 'https://developer.kustomer.com/kustomer-api-docs/reference/introduction', - apiUrl: 'https://api.kustomerapp.com', - }, - logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', - description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', - active: false, - authStrategy: AuthStrategy.api_key - }, - // todo - 'pivotal_tracker': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '', - }, - logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', - description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', - active: false, - }, - // todo - 'rally': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '', - }, - logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', - description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', - active: false, - }, - 'reamaze': { - scopes: '', - urls: { - docsUrl: 'https://www.reamaze.com/api', - apiUrl: '/api/v1', - }, - logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', - description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', - active: false, - authStrategy: AuthStrategy.api_key - }, - // todo - 'salesforce': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '', - }, - logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', - description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', - active: false, - }, - // todo - 'servicenow': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '', - }, - logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', - description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', - active: false, - }, - 'shortcut': { - scopes: '', - urls: { - docsUrl: 'https://developer.shortcut.com/api/rest/v3', - apiUrl: 'https://api.app.shortcut.com', - }, - logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', - description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', - active: false, - authStrategy: AuthStrategy.api_key - }, - // todo - 'spotdraft': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '', - }, - logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', - description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', - active: false, - }, - // todo - 'teamwork': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '', - }, - logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', - description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', - active: false, - }, - // todo - 'trello': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: 'https://api.app.shortcut.com', - }, - logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', - description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', - active: false, - }, - 'wrike': { - scopes: '', - urls: { - docsUrl: 'https://developers.wrike.com/overview/', - apiUrl: '/api/v4', - authBaseUrl: 'https://login.wrike.com/oauth2/authorize/v4', - }, - logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', - description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', - active: false, - authStrategy: AuthStrategy.oauth2 - }, - 'zoho_bugtracker': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '', - }, - logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', - description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', - active: false, - }, - 'zoho_desk': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '', - }, - logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRqz0aID6B-InxK_03P7tCtqpXNXdawBcro67CyEE0I5g&s', - description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users', - active: false, - }, - }, - 'accounting': { - 'pennylane': { - scopes: '', - urls: { - docsUrl: 'https://pennylane.readme.io/docs/getting-started', - apiUrl: 'https://app.pennylane.com/api/external/v1', - authBaseUrl: 'https://app.pennylane.com/oauth/authorize', - }, - logoPath: 'https://cdn-images-1.medium.com/max/1200/1*wk7CNGik_1Szbt7s1fNZxA.png', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - authStrategy: AuthStrategy.oauth2 - }, - 'freshbooks': { - scopes: '', - urls: { - docsUrl: 'https://www.freshbooks.com/api/start', - apiUrl: 'https://api.freshbooks.com', - authBaseUrl: 'https://auth.freshbooks.com/oauth/authorize', - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - authStrategy: AuthStrategy.oauth2 - }, - // todo - 'clearbooks': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '', - authBaseUrl: '', - }, - logoPath: 'https://s3-eu-west-1.amazonaws.com/clearbooks-marketing/media-centre/MediaCentre/clear-books/CMYK/icon/clear-books-icon-cmyk.png', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'freeagent': { - urls: { - docsUrl: 'https://dev.freeagent.com/docs/quick_start', - apiUrl: 'https://api.freeagent.com/v2', - authBaseUrl: 'https://api.freeagent.com/v2/approve_app', - }, - logoPath: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQU-fob0b9pBNQdm80usnYa2yWdagm3eeBDH-870vSmfg&s', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - authStrategy: AuthStrategy.oauth2 - }, - 'sage': { - scopes: '', - urls: { - docsUrl: 'https://developer.sage.com/accounting/reference/', - apiUrl: 'https://api.accounting.sage.com/v3.1', - authBaseUrl: 'https://www.sageone.com/oauth2/auth/central?filter=apiv3.1', - }, - logoPath: 'https://upload.wikimedia.org/wikipedia/en/thumb/b/b7/Sage_Group_logo_2022.svg/2560px-Sage_Group_logo_2022.svg.png', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - authStrategy: AuthStrategy.oauth2 - }, - // todo - 'sage_intacct': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '', - authBaseUrl: '', - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - // todo - 'microsoft_dynamics': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '', - authBaseUrl: '', - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'moneybird': { - scopes: '', - urls: { - docsUrl: 'https://developer.moneybird.com/', - apiUrl: 'https://moneybird.com/api/v2', - authBaseUrl: 'https://moneybird.com/oauth/authorize', - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - authStrategy: AuthStrategy.oauth2 - }, - // todo - 'netsuite': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '', - authBaseUrl: '', - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'quickbooks': { - scopes: '', - urls: { - docsUrl: 'https://developer.intuit.com/app/developer/qbo/docs/develop', - apiUrl: 'https://quickbooks.api.intuit.com/v3', - authBaseUrl: 'https://appcenter.intuit.com/connect/oauth2', - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - authStrategy: AuthStrategy.oauth2 - }, - // todo - 'workday': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '', - authBaseUrl: '', - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'wave_financial': { - scopes: '', - urls: { - docsUrl: 'https://developer.waveapps.com/hc/en-us/articles/360019968212-API-Reference', - apiUrl: 'https://gql.waveapps.com/graphql/public', - authBaseUrl: 'https://api.waveapps.com/oauth2/authorize/', - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - authStrategy: AuthStrategy.oauth2 - }, - }, - 'marketingautomation': { - 'active_campaign': { - scopes: '', - urls: { - docsUrl: 'https://developers.activecampaign.com/reference/overview', - apiUrl: '/api/3' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - authStrategy: AuthStrategy.api_key - }, - // todo - 'customerio': { - scopes: '', - urls: { - docsUrl: 'https://customer.io/docs/api/track/', - apiUrl: 'https://track.customer.io/api/' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'getresponse': { - urls: { - authBaseUrl: 'https://app.getresponse.com/oauth2_authorize.html', - docsUrl: 'https://apidocs.getresponse.com/v3', - apiUrl: 'https://api.getresponse.com/v3' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - authStrategy: AuthStrategy.oauth2 - }, - // todo - 'hubspot_marketing_hub': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - // todo - 'keap': { - scopes: '', - urls: { - authBaseUrl: 'https://accounts.infusionsoft.com/app/oauth/authorize', - docsUrl: 'https://developer.infusionsoft.com/docs/rest/', - apiUrl: 'https://api.infusionsoft.com/crm/rest/v1/account/profile' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - authStrategy: AuthStrategy.oauth2 - }, - 'klaviyo': { - scopes: '', - urls: { - docsUrl: 'https://developers.klaviyo.com/en/reference/api_overview', - apiUrl: 'https://a.klaviyo.com/api' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - authStrategy: AuthStrategy.api_key - }, - 'mailchimp': { - scopes: '', - urls: { - authBaseUrl: 'https://login.mailchimp.com/oauth2/authorize', - docsUrl: 'https://mailchimp.com/developer/marketing/api/', - apiUrl: '' // todo - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - authStrategy: AuthStrategy.oauth2 - }, - 'messagebird': { - scopes: '', - urls: { - docsUrl: 'https://developers.messagebird.com/api/', - apiUrl: 'https://rest.messagebird.com' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - authStrategy: AuthStrategy.api_key - }, - 'podium': { - scopes: '', - urls: { - authBaseUrl: 'https://api.podium.com/oauth/authorize', - docsUrl: 'https://docs.podium.com/reference/introduction', - apiUrl: 'https://api.podium.com/v4' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - authStrategy: AuthStrategy.oauth2 - }, - 'sendgrid': { - scopes: '', - urls: { - docsUrl: 'https://docs.sendgrid.com/for-developers/sending-email/api-getting-started', - apiUrl: 'https://api.sendgrid.com/v3' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - authStrategy: AuthStrategy.api_key - }, - 'brevo': { - scopes: '', - urls: { - docsUrl: 'https://developers.brevo.com/docs/getting-started', - apiUrl: 'https://api.brevo.com/v3' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - authStrategy: AuthStrategy.api_key - }, - }, - // todo - 'ats': { - // todo - 'applicantstack': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'ashby': { - scopes: '', - urls: { - docsUrl: 'https://developers.ashbyhq.com', - apiUrl: 'https://api.ashbyhq.com' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - authStrategy: AuthStrategy.api_key - }, - // todo - 'bamboohr': { - scopes: '', - urls: { - docsUrl: 'https://documentation.bamboohr.com/docs/getting-started', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - authStrategy: AuthStrategy.api_key - }, - 'breezy': { - scopes: '', - urls: { - docsUrl: 'https://developer.breezy.hr/reference/overview', - apiUrl: 'https://api.breezy.hr/v3' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - authStrategy: AuthStrategy.api_key - }, - // todo - 'bullhorn': { - scopes: '', - urls: { - docsUrl: 'https://bullhorn.github.io/rest-api-docs/', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'cats': { - scopes: '', - urls: { - docsUrl: 'https://docs.catsone.com/api/v3/', - apiUrl: 'https://api.catsone.com/v3' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - authStrategy: AuthStrategy.api_key - }, - 'clayhr': { - scopes: '', - urls: { - docsUrl: 'https://clayhr.readme.io/', - apiUrl: '/rm/api/v3' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - authStrategy: AuthStrategy.api_key - }, - // todo - 'clockwork': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - // todo - 'comeet': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'cornerstone_talentlink': { - scopes: '', - urls: { - docsUrl: 'https://developer.lumesse-talenthub.com/rest-api-developers-guide/1.21.33/index.html?page=rest-api&subpage=introduction', - apiUrl: 'https://apiproxy.shared.lumessetalentlink.com/tlk/rest' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - authStrategy: AuthStrategy.api_key - }, - 'engage_ats': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'eploy': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'fountain': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'freshteam': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'greenhouse': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'greenhouse_job_boards': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'harbour_ats': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'homerun': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'hrcloud': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'icims': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'infinite_brassring': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'jazzhr': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'jobadder': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'jobscore': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'jobvite': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'lano': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'lever': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'occupop': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'oracle_fusion': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'oracle_taleo': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'personio_recruiting': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'pinpoint': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'polymer': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'recruiterflow': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'recruitive': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'sage_hr': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'sap_successfactors': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'smartrecruiters': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'talentlyft': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'talentreef': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'teamtailor': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'tellent': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'tribepad': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'ukg_pro_recruiting': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'workable': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'workday': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'zoho_recruit': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - }, - // TODO - 'hris': { - '7shifts': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'adp_workforce_now': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'alexishr': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'alliancehcm': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'altera_payroll': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'bamboohr': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'breathe': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'ceridian_dayforce': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'charlie': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'charthop': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'clayhr': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'cyberark': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'deel': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'employment_hero': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'factorial': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'freshteam': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'google_workspace': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'gusto': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'hibob': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'hrcloud': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'hrpartner': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'humaans': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'humi': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'insperity_premier': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'active_campaign': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'intellli_hr': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'iris_cascade': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'jumpcloud': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'justworks': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'kallidus': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'keka': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'kenjo': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'lano': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'lucca': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'microsoft_entra_id': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'namely': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'nmbrs': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'officient': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'okta': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'onelogin': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'oracle_hcm': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'oyster_hr': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'paycaptain': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'paychex': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'paycor': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'payfit': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'paylocity': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'people_hr': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'personio': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'pingone': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'proliant': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'remote': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'sage_hr': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'sap_successfactors': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'sesame': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'square_payroll': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'trinet': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'trinet_hr_platform': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'ukg_pro': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'ukg_pro_workforce': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'ukg_ready': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'workday': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - 'zoho_people': { - scopes: '', - urls: { - docsUrl: '', - apiUrl: '' - }, - logoPath: 'https://play-lh.googleusercontent.com/EMobDJKabP1eY_63QHgPS_-TK3eRfxXaeOnERbcRaWAw573iaV74pXS9xOv997dRZtM', - description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, - }, - } -}; - function getActiveProvidersForVertical(vertical: string): VerticalConfig { - const verticalConfig = providersConfig[vertical.toLowerCase()]; + const verticalConfig = CONNECTORS_METADATA[vertical.toLowerCase()]; if (!verticalConfig) { return {}; } @@ -2138,7 +30,7 @@ function getActiveProvidersForVertical(vertical: string): VerticalConfig { } export const getDescription = (name: string): string | null => { - const vertical = findProviderVertical(name); + const vertical = findConnectorCategory(name); if (vertical == null) { return null; } @@ -2202,8 +94,8 @@ export function providersArray(vertical?: string): Provider[] { } } -export const findProviderVertical = (providerName: string): string | null => { - for (const [vertical, providers] of Object.entries(providersConfig)) { +export const findConnectorCategory = (providerName: string): string | null => { + for (const [vertical, providers] of Object.entries(CONNECTORS_METADATA)) { if (providers.hasOwnProperty.call(providers, providerName.toLowerCase())) { return vertical; } @@ -2212,8 +104,8 @@ export const findProviderVertical = (providerName: string): string | null => { }; export function findProviderByName(providerName: string): Provider | null { - for (const vertical in providersConfig) { - if (providersConfig.hasOwnProperty.call(providersConfig, vertical)) { + for (const vertical in CONNECTORS_METADATA) { + if (CONNECTORS_METADATA.hasOwnProperty.call(CONNECTORS_METADATA, vertical)) { const activeProviders = getActiveProvidersForVertical(vertical); if (activeProviders.hasOwnProperty.call(activeProviders, providerName)) { const providerDetails = activeProviders[providerName]; @@ -2228,11 +120,68 @@ export function findProviderByName(providerName: string): Provider | null { } export function getLogoURL(providerName: string): string { - const vertical = findProviderVertical(providerName); + const vertical = findConnectorCategory(providerName); if (vertical !== null) { - return providersConfig[vertical][providerName].logoPath + return CONNECTORS_METADATA[vertical][providerName].logoPath } return '' } + +export function mergeAllProviders(...arrays: string[][]): { vertical: string, value: string }[] { + const result: { vertical: string, value: string }[] = []; + arrays.forEach((arr, index) => { + const arrayName = Object.keys({ CRM_PROVIDERS, HRIS_PROVIDERS, ATS_PROVIDERS, ACCOUNTING_PROVIDERS, TICKETING_PROVIDERS, MARKETINGAUTOMATION_PROVIDERS, FILESTORAGE_PROVIDERS })[index]; + arr.forEach(item => { + if (item !== '') { + result.push({ vertical: arrayName.split('_')[0], value: item }); + } + }); + }); + return result; +} + +export const ALL_PROVIDERS: { vertical: string, value: string }[] = mergeAllProviders(CRM_PROVIDERS, HRIS_PROVIDERS, ATS_PROVIDERS, ACCOUNTING_PROVIDERS, TICKETING_PROVIDERS, MARKETINGAUTOMATION_PROVIDERS, FILESTORAGE_PROVIDERS) + +export function slugFromCategory(category: ConnectorCategory) { + switch(category) { + case ConnectorCategory.Crm: + return 'crm'; + case ConnectorCategory.Hris: + return 'hris'; + case ConnectorCategory.Ats: + return 'ats'; + case ConnectorCategory.Ticketing: + return 'tcg'; + case ConnectorCategory.MarketingAutomation: + return 'mktg'; + case ConnectorCategory.FileStorage: + return 'fstg'; + case ConnectorCategory.Accounting: + return 'actng'; + default: + return null; + } +} + +export function categoryFromSlug(slug: string): ConnectorCategory | null { + switch (slug) { + case 'crm': + return ConnectorCategory.Crm; + case 'hris': + return ConnectorCategory.Hris; + case 'ats': + return ConnectorCategory.Ats; + case 'tcg': + return ConnectorCategory.Ticketing; + case 'mktg': + return ConnectorCategory.MarketingAutomation; + case 'fstg': + return ConnectorCategory.FileStorage; + case 'actng': + return ConnectorCategory.Accounting; + default: + return null; + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b5f2a02a7..9acd05dcd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -69,6 +69,9 @@ importers: '@radix-ui/react-popover': specifier: ^1.0.7 version: 1.0.7(@types/react-dom@18.2.24)(@types/react@18.2.75)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-progress': + specifier: ^1.0.3 + version: 1.0.3(@types/react-dom@18.2.24)(@types/react@18.2.75)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-scroll-area': specifier: ^1.0.5 version: 1.0.5(@types/react-dom@18.2.24)(@types/react@18.2.75)(react-dom@18.2.0)(react@18.2.0) @@ -153,6 +156,9 @@ importers: react-dom: specifier: ^18 version: 18.2.0(react@18.2.0) + react-dropzone: + specifier: ^14.2.3 + version: 14.2.3(react@18.2.0) react-hook-form: specifier: ^7.51.2 version: 7.51.2(react@18.2.0) @@ -171,6 +177,9 @@ importers: tailwindcss-animate: specifier: ^1.0.7 version: 1.0.7(tailwindcss@3.4.3) + xlsx: + specifier: ^0.18.5 + version: 0.18.5 zod: specifier: ^3.22.4 version: 3.23.8 @@ -217,12 +226,27 @@ importers: '@panora/shared': specifier: workspace:^ version: link:../../../packages/shared + '@radix-ui/react-icons': + specifier: ^1.3.0 + version: 1.3.0(react@18.2.0) + '@radix-ui/react-slot': + specifier: ^1.0.2 + version: 1.0.2(@types/react@18.2.75)(react@18.2.0) '@tanstack/react-query': specifier: ^5.12.2 version: 5.29.0(react@18.2.0) + class-variance-authority: + specifier: ^0.7.0 + version: 0.7.0 + clsx: + specifier: ^2.1.0 + version: 2.1.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) + tailwind-merge: + specifier: ^2.2.1 + version: 2.2.2 devDependencies: '@types/react': specifier: ^18.2.52 @@ -257,12 +281,36 @@ importers: '@panora/typescript-sdk': specifier: ^1.0.3 version: 1.0.5 + '@radix-ui/react-label': + specifier: ^2.0.2 + version: 2.0.2(@types/react-dom@18.2.24)(@types/react@18.2.75)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-radio-group': + specifier: ^1.1.3 + version: 1.1.3(@types/react-dom@18.2.24)(@types/react@18.2.75)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-select': + specifier: ^2.0.0 + version: 2.0.0(@types/react-dom@18.2.24)(@types/react@18.2.75)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-separator': + specifier: ^1.0.3 + version: 1.0.3(@types/react-dom@18.2.24)(@types/react@18.2.75)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-slot': + specifier: ^1.0.2 + version: 1.0.2(@types/react@18.2.75)(react@18.2.0) '@tanstack/react-query': specifier: ^5.12.2 version: 5.29.0(react@18.2.0) api: specifier: workspace:* version: link:../../packages/api + class-variance-authority: + specifier: ^0.7.0 + version: 0.7.0 + clsx: + specifier: ^2.1.0 + version: 2.1.0 + lucide-react: + specifier: ^0.344.0 + version: 0.344.0(react@18.2.0) react: specifier: ^18.2.0 version: 18.2.0 @@ -272,13 +320,22 @@ importers: 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) + tailwind-merge: + specifier: ^2.2.1 + version: 2.2.2 tailwind-scrollbar-hide: specifier: ^1.1.7 version: 1.1.7 + tailwindcss-animate: + specifier: ^1.0.7 + version: 1.0.7(tailwindcss@3.4.3) uuid: specifier: ^9.0.1 version: 9.0.1 devDependencies: + '@types/node': + specifier: ^20.12.12 + version: 20.12.12 '@types/react': specifier: ^18.2.15 version: 18.2.75 @@ -320,7 +377,7 @@ importers: version: 5.4.4 vite: specifier: ^4.4.5 - version: 4.5.3 + version: 4.5.3(@types/node@20.12.12) packages/api: dependencies: @@ -3639,6 +3696,58 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@radix-ui/react-progress@1.0.3(@types/react-dom@18.2.24)(@types/react@18.2.75)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-5G6Om/tYSxjSeEdrb1VfKkfZfn/1IlPWd731h2RfPuSbIfNUgfqAwbKfJCg/PP6nuUCTrYzalwHSpSinoWoCag==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.24.4 + '@radix-ui/react-context': 1.0.1(@types/react@18.2.75)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.24)(@types/react@18.2.75)(react-dom@18.2.0)(react@18.2.0) + '@types/react': 18.2.75 + '@types/react-dom': 18.2.24 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /@radix-ui/react-radio-group@1.1.3(@types/react-dom@18.2.24)(@types/react@18.2.75)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-x+yELayyefNeKeTx4fjK6j99Fs6c4qKm3aY38G3swQVTN6xMpsrbigC0uHs2L//g8q4qR7qOcww8430jJmi2ag==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.24.4 + '@radix-ui/primitive': 1.0.1 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.75)(react@18.2.0) + '@radix-ui/react-context': 1.0.1(@types/react@18.2.75)(react@18.2.0) + '@radix-ui/react-direction': 1.0.1(@types/react@18.2.75)(react@18.2.0) + '@radix-ui/react-presence': 1.0.1(@types/react-dom@18.2.24)(@types/react@18.2.75)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.24)(@types/react@18.2.75)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-roving-focus': 1.0.4(@types/react-dom@18.2.24)(@types/react@18.2.75)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.75)(react@18.2.0) + '@radix-ui/react-use-previous': 1.0.1(@types/react@18.2.75)(react@18.2.0) + '@radix-ui/react-use-size': 1.0.1(@types/react@18.2.75)(react@18.2.0) + '@types/react': 18.2.75 + '@types/react-dom': 18.2.24 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@radix-ui/react-roving-focus@1.0.4(@types/react-dom@18.2.24)(@types/react@18.2.75)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-2mUg5Mgcu001VkGy+FfzZyzbmuUWzgWkj3rvv4yu+mLw03+mTzbxZHvfcGyFp2b8EkQeMkpRQ5FiA2Vr2O6TeQ==} peerDependencies: @@ -4659,6 +4768,12 @@ packages: undici-types: 5.26.5 dev: false + /@types/node@20.12.12: + resolution: {integrity: sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==} + dependencies: + undici-types: 5.26.5 + dev: true + /@types/node@20.12.7: resolution: {integrity: sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==} dependencies: @@ -5049,7 +5164,7 @@ packages: '@babel/plugin-transform-react-jsx-source': 7.24.1(@babel/core@7.24.4) '@types/babel__core': 7.20.5 react-refresh: 0.14.0 - vite: 4.5.3 + vite: 4.5.3(@types/node@20.12.12) transitivePeerDependencies: - supports-color dev: true @@ -5213,6 +5328,11 @@ packages: hasBin: true dev: true + /adler-32@1.3.1: + resolution: {integrity: sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==} + engines: {node: '>=0.8'} + dev: false + /agent-base@6.0.2: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} engines: {node: '>= 6.0.0'} @@ -5590,6 +5710,11 @@ packages: when-exit: 2.1.2 dev: false + /attr-accept@2.2.2: + resolution: {integrity: sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg==} + engines: {node: '>=4'} + dev: false + /autoprefixer@10.4.19(postcss@8.4.38): resolution: {integrity: sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==} engines: {node: ^10 || ^12 || >=14} @@ -5978,6 +6103,14 @@ packages: /caniuse-lite@1.0.30001607: resolution: {integrity: sha512-WcvhVRjXLKFB/kmOFVwELtMxyhq3iM/MvmXcyCe2PNf166c39mptscOc/45TTS96n2gpNV2z7+NakArTWZCQ3w==} + /cfb@1.2.2: + resolution: {integrity: sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==} + engines: {node: '>=0.8'} + dependencies: + adler-32: 1.3.1 + crc-32: 1.2.2 + dev: false + /chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} engines: {node: '>=4'} @@ -6178,6 +6311,11 @@ packages: engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} dev: true + /codepage@1.15.0: + resolution: {integrity: sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==} + engines: {node: '>=0.8'} + dev: false + /collect-v8-coverage@1.0.2: resolution: {integrity: sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==} dev: true @@ -6400,6 +6538,12 @@ packages: typescript: 5.3.3 dev: true + /crc-32@1.2.2: + resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==} + engines: {node: '>=0.8'} + hasBin: true + dev: false + /create-jest@29.7.0(@types/node@20.12.7)(ts-node@10.9.2): resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -7720,6 +7864,13 @@ packages: flat-cache: 3.2.0 dev: true + /file-selector@0.6.0: + resolution: {integrity: sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw==} + engines: {node: '>= 12'} + dependencies: + tslib: 2.6.2 + dev: false + /fill-range@7.0.1: resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} engines: {node: '>=8'} @@ -7864,6 +8015,11 @@ packages: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} + /frac@1.1.2: + resolution: {integrity: sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==} + engines: {node: '>=0.8'} + dev: false + /fraction.js@4.3.7: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} dev: true @@ -11526,6 +11682,18 @@ packages: react: 18.2.0 scheduler: 0.23.0 + /react-dropzone@14.2.3(react@18.2.0): + resolution: {integrity: sha512-O3om8I+PkFKbxCukfIR3QAGftYXDZfOE2N1mr/7qebQJHs7U+/RSL/9xomJNpRg9kM5h9soQSdf0Gc7OHF5Fug==} + engines: {node: '>= 10.13'} + peerDependencies: + react: '>= 16.8 || 18.0.0' + dependencies: + attr-accept: 2.2.2 + file-selector: 0.6.0 + prop-types: 15.8.1 + react: 18.2.0 + dev: false + /react-hook-form@7.51.2(react@18.2.0): resolution: {integrity: sha512-y++lwaWjtzDt/XNnyGDQy6goHskFualmDlf+jzEZvjvz6KWDf7EboL7pUvRCzPTJd0EOPpdekYaQLEvvG6m6HA==} engines: {node: '>=12.22.0'} @@ -12391,6 +12559,13 @@ packages: resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} dev: false + /ssf@0.11.2: + resolution: {integrity: sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==} + engines: {node: '>=0.8'} + dependencies: + frac: 1.1.2 + dev: false + /stack-utils@2.0.6: resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} engines: {node: '>=10'} @@ -13572,7 +13747,7 @@ packages: d3-timer: 3.0.1 dev: false - /vite@4.5.3: + /vite@4.5.3(@types/node@20.12.12): resolution: {integrity: sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==} engines: {node: ^14.18.0 || >=16.0.0} hasBin: true @@ -13600,6 +13775,7 @@ packages: terser: optional: true dependencies: + '@types/node': 20.12.12 esbuild: 0.18.20 postcss: 8.4.38 rollup: 3.29.4 @@ -13836,6 +14012,16 @@ packages: string-width: 5.1.2 dev: false + /wmf@1.0.2: + resolution: {integrity: sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==} + engines: {node: '>=0.8'} + dev: false + + /word@0.3.0: + resolution: {integrity: sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==} + engines: {node: '>=0.8'} + dev: false + /wrap-ansi@6.2.0: resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} engines: {node: '>=8'} @@ -13924,6 +14110,20 @@ packages: engines: {node: '>=12'} dev: false + /xlsx@0.18.5: + resolution: {integrity: sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==} + engines: {node: '>=0.8'} + hasBin: true + dependencies: + adler-32: 1.3.1 + cfb: 1.2.2 + codepage: 1.15.0 + crc-32: 1.2.2 + ssf: 0.11.2 + wmf: 1.0.2 + word: 0.3.0 + dev: false + /xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'}