From 3455ce9a6135afa9198e868b5728a74b514bf874 Mon Sep 17 00:00:00 2001 From: nael Date: Sun, 12 Nov 2023 22:30:43 +0100 Subject: [PATCH 1/7] feat: frontend_snippet > hubspot oauth flow --- .../connections/connections.controller.ts | 48 +++ .../@core/connections/connections.module.ts | 2 + .../crm/crm-connection.controller.ts | 30 -- .../connections/crm/crm-connection.module.ts | 2 - packages/api/src/@core/utils/providers.ts | 48 +++ packages/frontend-snippet/.env.example | 1 + packages/frontend-snippet/.eslintrc.cjs | 18 ++ packages/frontend-snippet/.gitignore | 26 ++ packages/frontend-snippet/README.md | 27 ++ packages/frontend-snippet/index.html | 13 + packages/frontend-snippet/package.json | 28 ++ packages/frontend-snippet/public/vite.svg | 1 + packages/frontend-snippet/src/App.css | 42 +++ packages/frontend-snippet/src/App.tsx | 28 ++ .../frontend-snippet/src/assets/react.svg | 1 + .../frontend-snippet/src/hooks/useOAuth.ts | 33 +++ packages/frontend-snippet/src/index.css | 69 +++++ packages/frontend-snippet/src/main.tsx | 10 + packages/frontend-snippet/src/vite-env.d.ts | 1 + packages/frontend-snippet/tsconfig.json | 25 ++ packages/frontend-snippet/tsconfig.node.json | 10 + packages/frontend-snippet/vite.config.ts | 7 + pnpm-lock.yaml | 274 +++++++++++++++++- 23 files changed, 709 insertions(+), 35 deletions(-) create mode 100644 packages/api/src/@core/connections/connections.controller.ts delete mode 100644 packages/api/src/@core/connections/crm/crm-connection.controller.ts create mode 100644 packages/api/src/@core/utils/providers.ts create mode 100644 packages/frontend-snippet/.env.example create mode 100644 packages/frontend-snippet/.eslintrc.cjs create mode 100644 packages/frontend-snippet/.gitignore create mode 100644 packages/frontend-snippet/README.md create mode 100644 packages/frontend-snippet/index.html create mode 100644 packages/frontend-snippet/package.json create mode 100644 packages/frontend-snippet/public/vite.svg create mode 100644 packages/frontend-snippet/src/App.css create mode 100644 packages/frontend-snippet/src/App.tsx create mode 100644 packages/frontend-snippet/src/assets/react.svg create mode 100644 packages/frontend-snippet/src/hooks/useOAuth.ts create mode 100644 packages/frontend-snippet/src/index.css create mode 100644 packages/frontend-snippet/src/main.tsx create mode 100644 packages/frontend-snippet/src/vite-env.d.ts create mode 100644 packages/frontend-snippet/tsconfig.json create mode 100644 packages/frontend-snippet/tsconfig.node.json create mode 100644 packages/frontend-snippet/vite.config.ts diff --git a/packages/api/src/@core/connections/connections.controller.ts b/packages/api/src/@core/connections/connections.controller.ts new file mode 100644 index 000000000..d62418cf7 --- /dev/null +++ b/packages/api/src/@core/connections/connections.controller.ts @@ -0,0 +1,48 @@ +import { Controller, Get, Query, Res } from '@nestjs/common'; +import { Response } from 'express'; // Importing the Express Response type for type checking +import { CrmConnectionsService } from './crm/services/crm-connection.service'; +import { ProviderVertical, getProviderVertical } from '../utils/providers'; + +@Controller('connections') +export class ConnectionsController { + constructor(private readonly crmConnectionsService: CrmConnectionsService) {} + + @Get('oauth/callback') + handleCallback( + @Res() res: Response, + @Query('projectId') projectId: string, + @Query('linkedUserId') linkedUserId: string, + @Query('providerName') providerName: string, + @Query('code') code: string, + @Query('returnUrl') returnUrl: string, + @Query('accountURL') zohoAccountURL?: string, + ) { + //TODO; ADD VERIFICATION OF PARAMS + switch (getProviderVertical(providerName)) { + case ProviderVertical.CRM: + this.crmConnectionsService.handleCRMCallBack( + projectId, + linkedUserId, + providerName, + code, + zohoAccountURL, + ); + case ProviderVertical.ATS: + break; + case ProviderVertical.Accounting: + break; + case ProviderVertical.FileStorage: + break; + case ProviderVertical.HRIS: + break; + case ProviderVertical.MarketingAutomation: + break; + case ProviderVertical.Ticketing: + break; + case ProviderVertical.Unknown: + break; + } + + res.redirect(returnUrl); + } +} diff --git a/packages/api/src/@core/connections/connections.module.ts b/packages/api/src/@core/connections/connections.module.ts index f3072162d..da2fd4f3c 100644 --- a/packages/api/src/@core/connections/connections.module.ts +++ b/packages/api/src/@core/connections/connections.module.ts @@ -1,7 +1,9 @@ import { Module } from '@nestjs/common'; import { CrmConnectionModule } from './crm/crm-connection.module'; +import { ConnectionsController } from './connections.controller'; @Module({ + controllers: [ConnectionsController], imports: [CrmConnectionModule], }) export class ConnectionsModule {} diff --git a/packages/api/src/@core/connections/crm/crm-connection.controller.ts b/packages/api/src/@core/connections/crm/crm-connection.controller.ts deleted file mode 100644 index 1d1255d54..000000000 --- a/packages/api/src/@core/connections/crm/crm-connection.controller.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Controller, Get, Query, Res } from '@nestjs/common'; -import { Response } from 'express'; // Importing the Express Response type for type checking -import { CrmConnectionsService } from './services/crm-connection.service'; - -@Controller('connections/crm') -export class CrmConnectionsController { - constructor(private readonly crmConnectionsService: CrmConnectionsService) {} - - @Get('oauth/callback') - handleCRMCallback( - @Res() res: Response, - @Query('projectId') projectId: string, - @Query('linkedUserId') linkedUserId: string, - @Query('providerName') providerName: string, - @Query('returnUrl') returnUrl: string, - @Query('code') code: string, - @Query('accountURL') zohoAccountURL?: string, - ) { - //TODO; ADD VERIFICATION OF PARAMS - - this.crmConnectionsService.handleCRMCallBack( - projectId, - linkedUserId, - providerName, - code, - zohoAccountURL, - ); - res.redirect(returnUrl); - } -} diff --git a/packages/api/src/@core/connections/crm/crm-connection.module.ts b/packages/api/src/@core/connections/crm/crm-connection.module.ts index f0f29a404..5f141df48 100644 --- a/packages/api/src/@core/connections/crm/crm-connection.module.ts +++ b/packages/api/src/@core/connections/crm/crm-connection.module.ts @@ -1,5 +1,4 @@ import { Module } from '@nestjs/common'; -import { CrmConnectionsController } from './crm-connection.controller'; import { CrmConnectionsService } from './services/crm-connection.service'; import { PrismaService } from 'src/@core/prisma/prisma.service'; import { FreshsalesConnectionService } from './services/freshsales/freshsales.service'; @@ -10,7 +9,6 @@ import { ZohoConnectionService } from './services/zoho/zoho.service'; import { LoggerService } from 'src/@core/logger/logger.service'; @Module({ - controllers: [CrmConnectionsController], providers: [ CrmConnectionsService, PrismaService, diff --git a/packages/api/src/@core/utils/providers.ts b/packages/api/src/@core/utils/providers.ts new file mode 100644 index 000000000..3f9ba3a76 --- /dev/null +++ b/packages/api/src/@core/utils/providers.ts @@ -0,0 +1,48 @@ +export const CRM_PROVIDERS = [ + 'zoho', + 'zendesk', + 'hubspot', + 'pipedrive', + 'freshsales', +]; +export const HRIS_PROVIDERS = []; +export const ATS_PROVIDERS = []; +export const ACCOUNTING_PROVIDERS = []; +export const TICKETING_PROVIDERS = []; +export const MARKETING_AUTOMATION_PROVIDERS = []; +export const FILE_STORAGE_PROVIDERS = []; + +export enum ProviderVertical { + CRM = 'CRM', + HRIS = 'HRIS', + ATS = 'ATS', + Accounting = 'Accounting', + Ticketing = 'Ticketing', + MarketingAutomation = 'Marketing Automation', + FileStorage = 'File Storage', + Unknown = 'Unknown', // Used if the provider does not match any category +} +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 (MARKETING_AUTOMATION_PROVIDERS.includes(providerName)) { + return ProviderVertical.MarketingAutomation; + } + if (FILE_STORAGE_PROVIDERS.includes(providerName)) { + return ProviderVertical.FileStorage; + } + return ProviderVertical.Unknown; +} diff --git a/packages/frontend-snippet/.env.example b/packages/frontend-snippet/.env.example new file mode 100644 index 000000000..e8b645c74 --- /dev/null +++ b/packages/frontend-snippet/.env.example @@ -0,0 +1 @@ +CLIENT_ID= #hubspot client id \ No newline at end of file diff --git a/packages/frontend-snippet/.eslintrc.cjs b/packages/frontend-snippet/.eslintrc.cjs new file mode 100644 index 000000000..d6c953795 --- /dev/null +++ b/packages/frontend-snippet/.eslintrc.cjs @@ -0,0 +1,18 @@ +module.exports = { + root: true, + env: { browser: true, es2020: true }, + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:react-hooks/recommended', + ], + ignorePatterns: ['dist', '.eslintrc.cjs'], + parser: '@typescript-eslint/parser', + plugins: ['react-refresh'], + rules: { + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, +} diff --git a/packages/frontend-snippet/.gitignore b/packages/frontend-snippet/.gitignore new file mode 100644 index 000000000..3b0b40372 --- /dev/null +++ b/packages/frontend-snippet/.gitignore @@ -0,0 +1,26 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +.env \ No newline at end of file diff --git a/packages/frontend-snippet/README.md b/packages/frontend-snippet/README.md new file mode 100644 index 000000000..1ebe379f5 --- /dev/null +++ b/packages/frontend-snippet/README.md @@ -0,0 +1,27 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: + +- Configure the top-level `parserOptions` property like this: + +```js + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + project: ['./tsconfig.json', './tsconfig.node.json'], + tsconfigRootDir: __dirname, + }, +``` + +- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked` +- Optionally add `plugin:@typescript-eslint/stylistic-type-checked` +- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list diff --git a/packages/frontend-snippet/index.html b/packages/frontend-snippet/index.html new file mode 100644 index 000000000..e4b78eae1 --- /dev/null +++ b/packages/frontend-snippet/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + React + TS + + +
+ + + diff --git a/packages/frontend-snippet/package.json b/packages/frontend-snippet/package.json new file mode 100644 index 000000000..3716a6d8c --- /dev/null +++ b/packages/frontend-snippet/package.json @@ -0,0 +1,28 @@ +{ + "name": "frontend-snippet", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview" + }, + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/react": "^18.2.15", + "@types/react-dom": "^18.2.7", + "@typescript-eslint/eslint-plugin": "^6.0.0", + "@typescript-eslint/parser": "^6.0.0", + "@vitejs/plugin-react": "^4.0.3", + "eslint": "^8.45.0", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.3", + "typescript": "^5.0.2", + "vite": "^4.4.5" + } +} diff --git a/packages/frontend-snippet/public/vite.svg b/packages/frontend-snippet/public/vite.svg new file mode 100644 index 000000000..e7b8dfb1b --- /dev/null +++ b/packages/frontend-snippet/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/frontend-snippet/src/App.css b/packages/frontend-snippet/src/App.css new file mode 100644 index 000000000..b9d355df2 --- /dev/null +++ b/packages/frontend-snippet/src/App.css @@ -0,0 +1,42 @@ +#root { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +@keyframes logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: no-preference) { + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; + } +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} diff --git a/packages/frontend-snippet/src/App.tsx b/packages/frontend-snippet/src/App.tsx new file mode 100644 index 000000000..b99310cf1 --- /dev/null +++ b/packages/frontend-snippet/src/App.tsx @@ -0,0 +1,28 @@ +import './App.css' +import useOAuth from './hooks/useOAuth'; + +const CLIENT_ID = import.meta.env.VITE_CLIENT_ID; // Replace with your OAuth client ID +const SCOPES = 'crm.lists.read%20crm.objects.contacts.read%20crm.objects.contacts.write%20crm.objects.custom.read%20crm.objects.custom.write%20crm.objects.companies.write%20crm.schemas.contacts.read%20crm.objects.feedback_submissions.read%20crm.lists.write%20crm.objects.companies.read%20crm.objects.deals.read%20crm.objects.deals.write%20crm.schemas.contacts.write'; // Replace with your requested scopes +const REDIRECT_URI = 'http://localhost:3000/oauth/callback'; // Replace with your redirect URI + +function App() { + const { open, isReady } = useOAuth({ + linkToken: 'ADD_GENERATED_LINK_TOKEN', // Replace with your link token + clientId: CLIENT_ID!, + scopes: SCOPES, + redirectUri: REDIRECT_URI, + onSuccess: () => console.log('OAuth successful') + }); + return ( + <> +

Integrations Flow

+
+ +
+ + ) +} + +export default App diff --git a/packages/frontend-snippet/src/assets/react.svg b/packages/frontend-snippet/src/assets/react.svg new file mode 100644 index 000000000..6c87de9bb --- /dev/null +++ b/packages/frontend-snippet/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/frontend-snippet/src/hooks/useOAuth.ts b/packages/frontend-snippet/src/hooks/useOAuth.ts new file mode 100644 index 000000000..409c126cf --- /dev/null +++ b/packages/frontend-snippet/src/hooks/useOAuth.ts @@ -0,0 +1,33 @@ +import { useState, useEffect } from 'react'; + +type UseOAuthProps = { + linkToken: string; + clientId: string; // Your OAuth client ID + scopes: string; // The scopes you are requesting + redirectUri: string; // The redirect URI registered with the OAuth provider + onSuccess: () => void; +}; + +const useOAuth = ({ linkToken, clientId, scopes, redirectUri, onSuccess }: UseOAuthProps) => { + const [isReady, setIsReady] = useState(false); + + useEffect(() => { + // Perform any setup logic here + setTimeout(() => setIsReady(true), 1000); // Simulating async operation + }, []); + + const openModal = () => { + //const authUrl = `https://app-eu1.hubspot.com/oauth/authorize?client_id=${encodeURIComponent(clientId)}&scope=${encodeURIComponent(scopes)}&redirect_uri=${encodeURIComponent(redirectUri)}`; + const authUrl = `https://app-eu1.hubspot.com/oauth/authorize?client_id=${clientId}&redirect_uri=http://localhost:3000/oauth/callback&scope=crm.lists.read%20crm.lists.write` + const width = 600, height = 600; + const left = (window.innerWidth - width) / 2; + const top = (window.innerHeight - height) / 2; + window.open(authUrl, 'OAuth', `width=${width},height=${height},top=${top},left=${left}`); + + // Call the onSuccess function here if needed, or after the OAuth flow is completed + }; + + return { open: openModal, isReady }; +}; + +export default useOAuth; diff --git a/packages/frontend-snippet/src/index.css b/packages/frontend-snippet/src/index.css new file mode 100644 index 000000000..2c3fac689 --- /dev/null +++ b/packages/frontend-snippet/src/index.css @@ -0,0 +1,69 @@ +: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: #242424; + + 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; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +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: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/packages/frontend-snippet/src/main.tsx b/packages/frontend-snippet/src/main.tsx new file mode 100644 index 000000000..3d7150da8 --- /dev/null +++ b/packages/frontend-snippet/src/main.tsx @@ -0,0 +1,10 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App.tsx' +import './index.css' + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + , +) diff --git a/packages/frontend-snippet/src/vite-env.d.ts b/packages/frontend-snippet/src/vite-env.d.ts new file mode 100644 index 000000000..11f02fe2a --- /dev/null +++ b/packages/frontend-snippet/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/packages/frontend-snippet/tsconfig.json b/packages/frontend-snippet/tsconfig.json new file mode 100644 index 000000000..a7fc6fbf2 --- /dev/null +++ b/packages/frontend-snippet/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/packages/frontend-snippet/tsconfig.node.json b/packages/frontend-snippet/tsconfig.node.json new file mode 100644 index 000000000..42872c59f --- /dev/null +++ b/packages/frontend-snippet/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/packages/frontend-snippet/vite.config.ts b/packages/frontend-snippet/vite.config.ts new file mode 100644 index 000000000..5a33944a9 --- /dev/null +++ b/packages/frontend-snippet/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 04b619f97..cfcd6216e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -172,6 +172,46 @@ importers: specifier: ^5.1.3 version: 5.1.3 + packages/frontend-snippet: + dependencies: + react: + specifier: ^18.2.0 + version: 18.2.0 + react-dom: + specifier: ^18.2.0 + version: 18.2.0(react@18.2.0) + devDependencies: + '@types/react': + specifier: ^18.2.15 + version: 18.2.33 + '@types/react-dom': + specifier: ^18.2.7 + version: 18.2.7 + '@typescript-eslint/eslint-plugin': + specifier: ^6.0.0 + version: 6.9.1(@typescript-eslint/parser@6.9.1)(eslint@8.52.0)(typescript@5.1.3) + '@typescript-eslint/parser': + specifier: ^6.0.0 + version: 6.9.1(eslint@8.52.0)(typescript@5.1.3) + '@vitejs/plugin-react': + specifier: ^4.0.3 + version: 4.0.3(vite@4.4.5) + eslint: + specifier: ^8.45.0 + version: 8.52.0 + eslint-plugin-react-hooks: + specifier: ^4.6.0 + version: 4.6.0(eslint@8.52.0) + eslint-plugin-react-refresh: + specifier: ^0.4.3 + version: 0.4.3(eslint@8.52.0) + typescript: + specifier: ^5.0.2 + version: 5.1.3 + vite: + specifier: ^4.4.5 + version: 4.4.5 + packages/sdk: dependencies: axios: @@ -1435,6 +1475,26 @@ packages: '@babel/plugin-transform-react-jsx': 7.22.15(@babel/core@7.23.2) dev: false + /@babel/plugin-transform-react-jsx-self@7.23.3(@babel/core@7.23.2): + resolution: {integrity: sha512-qXRvbeKDSfwnlJnanVRp0SfuWE5DQhwQr5xtLBzp56Wabyo+4CMosF6Kfp+eOD/4FYpql64XVJ2W0pVLlJZxOQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.2 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-transform-react-jsx-source@7.23.3(@babel/core@7.23.2): + resolution: {integrity: sha512-91RS0MDnAWDNvGC6Wio5XYkyWI39FMFO+JK9+4AlgaTH+yWwVTsw7/sn6LK0lH7c5F+TFkpv/3LfCJ1Ydwof/g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.2 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + /@babel/plugin-transform-react-jsx@7.22.15(@babel/core@7.23.2): resolution: {integrity: sha512-oKckg2eZFa8771O/5vi7XeTvmM6+O9cxZu+kanTU7tD4sin5nO/G8jGJhq8Hvt2Z0kUoEDRayuZLaUlYl8QuGA==} engines: {node: '>=6.9.0'} @@ -4204,6 +4264,12 @@ packages: /@types/range-parser@1.2.6: resolution: {integrity: sha512-+0autS93xyXizIYiyL02FCY8N+KkKPhILhcUSA276HxzreZ16kl+cmwvV2qAM/PuCCwPXzOXOWhiPcw20uSFcA==} + /@types/react-dom@18.2.7: + resolution: {integrity: sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA==} + dependencies: + '@types/react': 18.2.33 + dev: true + /@types/react-router-config@5.0.9: resolution: {integrity: sha512-a7zOj9yVUtM3Ns5stoseQAAsmppNxZpXDv6tZiFV5qlRmV4W96u53on1vApBX1eRSc8mrFOiB54Hc0Pk1J8GFg==} dependencies: @@ -4402,6 +4468,35 @@ packages: - supports-color dev: true + /@typescript-eslint/eslint-plugin@6.9.1(@typescript-eslint/parser@6.9.1)(eslint@8.52.0)(typescript@5.1.3): + resolution: {integrity: sha512-w0tiiRc9I4S5XSXXrMHOWgHgxbrBn1Ro+PmiYhSg2ZVdxrAJtQgzU5o2m1BfP6UOn7Vxcc6152vFjQfmZR4xEg==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@eslint-community/regexpp': 4.10.0 + '@typescript-eslint/parser': 6.9.1(eslint@8.52.0)(typescript@5.1.3) + '@typescript-eslint/scope-manager': 6.9.1 + '@typescript-eslint/type-utils': 6.9.1(eslint@8.52.0)(typescript@5.1.3) + '@typescript-eslint/utils': 6.9.1(eslint@8.52.0)(typescript@5.1.3) + '@typescript-eslint/visitor-keys': 6.9.1 + debug: 4.3.4 + eslint: 8.52.0 + graphemer: 1.4.0 + ignore: 5.2.4 + natural-compare: 1.4.0 + semver: 7.5.4 + ts-api-utils: 1.0.3(typescript@5.1.3) + typescript: 5.1.3 + transitivePeerDependencies: + - supports-color + dev: true + /@typescript-eslint/parser@5.59.11(eslint@8.52.0)(typescript@5.1.3): resolution: {integrity: sha512-s9ZF3M+Nym6CAZEkJJeO2TFHHDsKAM3ecNkLuH4i4s8/RCPnF5JRip2GyviYkeEAcwGMJxkqG9h2dAsnA1nZpA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -4464,6 +4559,27 @@ packages: - supports-color dev: true + /@typescript-eslint/parser@6.9.1(eslint@8.52.0)(typescript@5.1.3): + resolution: {integrity: sha512-C7AK2wn43GSaCUZ9do6Ksgi2g3mwFkMO3Cis96kzmgudoVaKyt62yNzJOktP0HDLb/iO2O0n2lBOzJgr6Q/cyg==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/scope-manager': 6.9.1 + '@typescript-eslint/types': 6.9.1 + '@typescript-eslint/typescript-estree': 6.9.1(typescript@5.1.3) + '@typescript-eslint/visitor-keys': 6.9.1 + debug: 4.3.4 + eslint: 8.52.0 + typescript: 5.1.3 + transitivePeerDependencies: + - supports-color + dev: true + /@typescript-eslint/scope-manager@5.59.11: resolution: {integrity: sha512-dHFOsxoLFtrIcSj5h0QoBT/89hxQONwmn3FOQ0GOQcLOOXm+MIrS8zEAhs4tWl5MraxCY3ZJpaXQQdFMc2Tu+Q==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -4548,6 +4664,26 @@ packages: - supports-color dev: true + /@typescript-eslint/type-utils@6.9.1(eslint@8.52.0)(typescript@5.1.3): + resolution: {integrity: sha512-eh2oHaUKCK58qIeYp19F5V5TbpM52680sB4zNSz29VBQPTWIlE/hCj5P5B1AChxECe/fmZlspAWFuRniep1Skg==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/typescript-estree': 6.9.1(typescript@5.1.3) + '@typescript-eslint/utils': 6.9.1(eslint@8.52.0)(typescript@5.1.3) + debug: 4.3.4 + eslint: 8.52.0 + ts-api-utils: 1.0.3(typescript@5.1.3) + typescript: 5.1.3 + transitivePeerDependencies: + - supports-color + dev: true + /@typescript-eslint/types@5.59.11: resolution: {integrity: sha512-epoN6R6tkvBYSc+cllrz+c2sOFWkbisJZWkOE+y3xHtvYaOE6Wk6B8e114McRJwFRjGvYdJwLXQH5c9osME/AA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -4626,6 +4762,27 @@ packages: - supports-color dev: true + /@typescript-eslint/typescript-estree@6.9.1(typescript@5.1.3): + resolution: {integrity: sha512-U+mUylTHfcqeO7mLWVQ5W/tMLXqVpRv61wm9ZtfE5egz7gtnmqVIw9ryh0mgIlkKk9rZLY3UHygsBSdB9/ftyw==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/types': 6.9.1 + '@typescript-eslint/visitor-keys': 6.9.1 + debug: 4.3.4 + globby: 11.1.0 + is-glob: 4.0.3 + semver: 7.5.4 + ts-api-utils: 1.0.3(typescript@5.1.3) + typescript: 5.1.3 + transitivePeerDependencies: + - supports-color + dev: true + /@typescript-eslint/utils@5.59.11(eslint@8.52.0)(typescript@5.1.3): resolution: {integrity: sha512-didu2rHSOMUdJThLk4aZ1Or8IcO3HzCw/ZvEjTTIfjIrcdd5cvSIwwDy2AOlE7htSNp7QIZ10fLMyRCveesMLg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -4684,6 +4841,25 @@ packages: - typescript dev: true + /@typescript-eslint/utils@6.9.1(eslint@8.52.0)(typescript@5.1.3): + resolution: {integrity: sha512-L1T0A5nFdQrMVunpZgzqPL6y2wVreSyHhKGZryS6jrEN7bD9NplVAyMryUhXsQ4TWLnZmxc2ekar/lSGIlprCA==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.52.0) + '@types/json-schema': 7.0.14 + '@types/semver': 7.5.4 + '@typescript-eslint/scope-manager': 6.9.1 + '@typescript-eslint/types': 6.9.1 + '@typescript-eslint/typescript-estree': 6.9.1(typescript@5.1.3) + eslint: 8.52.0 + semver: 7.5.4 + transitivePeerDependencies: + - supports-color + - typescript + dev: true + /@typescript-eslint/visitor-keys@5.59.11: resolution: {integrity: sha512-KGYniTGG3AMTuKF9QBD7EIrvufkB6O6uX3knP73xbKLMpH+QRPcgnCxjWXSHjMRuOxFLovljqQgQpR0c7GvjoA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -4711,6 +4887,21 @@ packages: /@ungap/structured-clone@1.2.0: resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} + /@vitejs/plugin-react@4.0.3(vite@4.4.5): + resolution: {integrity: sha512-pwXDog5nwwvSIzwrvYYmA2Ljcd/ZNlcsSG2Q9CNDBwnsd55UGAyr2doXtB5j+2uymRCnCfExlznzzSFbBRcoCg==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.2.0 + dependencies: + '@babel/core': 7.23.2 + '@babel/plugin-transform-react-jsx-self': 7.23.3(@babel/core@7.23.2) + '@babel/plugin-transform-react-jsx-source': 7.23.3(@babel/core@7.23.2) + react-refresh: 0.14.0 + vite: 4.4.5 + transitivePeerDependencies: + - supports-color + dev: true + /@webassemblyjs/ast@1.11.6: resolution: {integrity: sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==} dependencies: @@ -6940,6 +7131,23 @@ packages: prettier-linter-helpers: 1.0.0 dev: true + /eslint-plugin-react-hooks@4.6.0(eslint@8.52.0): + resolution: {integrity: sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==} + engines: {node: '>=10'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 + dependencies: + eslint: 8.52.0 + dev: true + + /eslint-plugin-react-refresh@0.4.3(eslint@8.52.0): + resolution: {integrity: sha512-Hh0wv8bUNY877+sI0BlCUlsS0TYYQqvzEwJsJJPM2WF4RnTStSnSR3zdJYa2nPOJgg3UghXi54lVyMSmpCalzA==} + peerDependencies: + eslint: '>=7' + dependencies: + eslint: 8.52.0 + dev: true + /eslint-plugin-react@7.33.2(eslint@8.52.0): resolution: {integrity: sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw==} engines: {node: '>=4'} @@ -9924,7 +10132,6 @@ packages: resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - dev: false /natural-compare-lite@1.4.0: resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} @@ -10971,7 +11178,6 @@ packages: nanoid: 3.3.6 picocolors: 1.0.0 source-map-js: 1.0.2 - dev: false /preferred-pm@3.1.2: resolution: {integrity: sha512-nk7dKrcW8hfCZ4H6klWcdRknBOXWzNQByJ0oJyX97BOupsYD+FzLS4hflgEu/uPUEHZCuRfMxzCBsuWd7OzT8Q==} @@ -11262,6 +11468,16 @@ packages: react: 17.0.2 scheduler: 0.20.2 + /react-dom@18.2.0(react@18.2.0): + resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} + peerDependencies: + react: ^18.2.0 + dependencies: + loose-envify: 1.4.0 + react: 18.2.0 + scheduler: 0.23.0 + dev: false + /react-error-overlay@6.0.11: resolution: {integrity: sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==} dev: false @@ -11323,6 +11539,11 @@ packages: webpack: 5.89.0 dev: false + /react-refresh@0.14.0: + resolution: {integrity: sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==} + engines: {node: '>=0.10.0'} + dev: true + /react-router-config@5.1.1(react-router@5.3.4)(react@17.0.2): resolution: {integrity: sha512-DuanZjaD8mQp1ppHjgnnUnyOlqYXZVjnov/JzFhjLEwd3Z4dYjMSnqrEzzGThH47vpCOqPPwJM2FtthLeJ8Pbg==} peerDependencies: @@ -11387,6 +11608,13 @@ packages: loose-envify: 1.4.0 object-assign: 4.1.1 + /react@18.2.0: + resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} + engines: {node: '>=0.10.0'} + dependencies: + loose-envify: 1.4.0 + dev: false + /read-pkg-up@7.0.1: resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} engines: {node: '>=8'} @@ -11818,6 +12046,12 @@ packages: loose-envify: 1.4.0 object-assign: 4.1.1 + /scheduler@0.23.0: + resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==} + dependencies: + loose-envify: 1.4.0 + dev: false + /schema-utils@2.7.0: resolution: {integrity: sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==} engines: {node: '>= 8.9.0'} @@ -12135,7 +12369,6 @@ packages: /source-map-js@1.0.2: resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} engines: {node: '>=0.10.0'} - dev: false /source-map-support@0.5.13: resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} @@ -13335,6 +13568,41 @@ packages: vfile-message: 2.0.4 dev: false + /vite@4.4.5: + resolution: {integrity: sha512-4m5kEtAWHYr0O1Fu7rZp64CfO1PsRGZlD3TAB32UmQlpd7qg15VF7ROqGN5CyqN7HFuwr7ICNM2+fDWRqFEKaA==} + engines: {node: ^14.18.0 || >=16.0.0} + hasBin: true + peerDependencies: + '@types/node': '>= 14' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + dependencies: + esbuild: 0.18.20 + postcss: 8.4.31 + rollup: 3.29.4 + optionalDependencies: + fsevents: 2.3.3 + dev: true + /wait-on@6.0.1: resolution: {integrity: sha512-zht+KASY3usTY5u2LgaNqn/Cd8MukxLGjdcZxT2ns5QzDmTFc4XoWBgC+C/na+sMRZTuVygQoMYwdcVjHnYIVw==} engines: {node: '>=10.0.0'} From 0618745461b653661120df558f9f3d1055a75224 Mon Sep 17 00:00:00 2001 From: nael Date: Mon, 13 Nov 2023 11:44:40 +0100 Subject: [PATCH 2/7] feat: frontend-snippet --- packages/frontend-snippet/src/App.tsx | 64 ++++++++++++++++--- .../frontend-snippet/src/helpers/utils.ts | 42 ++++++++++++ .../frontend-snippet/src/hooks/useOAuth.ts | 37 +++++++++-- packages/frontend-snippet/src/index.css | 24 +++++++ .../src/lib/ProviderModal.tsx | 29 +++++++++ 5 files changed, 180 insertions(+), 16 deletions(-) create mode 100644 packages/frontend-snippet/src/helpers/utils.ts create mode 100644 packages/frontend-snippet/src/lib/ProviderModal.tsx diff --git a/packages/frontend-snippet/src/App.tsx b/packages/frontend-snippet/src/App.tsx index b99310cf1..c98d99134 100644 --- a/packages/frontend-snippet/src/App.tsx +++ b/packages/frontend-snippet/src/App.tsx @@ -1,25 +1,71 @@ +import { useState } from 'react'; import './App.css' import useOAuth from './hooks/useOAuth'; - -const CLIENT_ID = import.meta.env.VITE_CLIENT_ID; // Replace with your OAuth client ID -const SCOPES = 'crm.lists.read%20crm.objects.contacts.read%20crm.objects.contacts.write%20crm.objects.custom.read%20crm.objects.custom.write%20crm.objects.companies.write%20crm.schemas.contacts.read%20crm.objects.feedback_submissions.read%20crm.lists.write%20crm.objects.companies.read%20crm.objects.deals.read%20crm.objects.deals.write%20crm.schemas.contacts.write'; // Replace with your requested scopes -const REDIRECT_URI = 'http://localhost:3000/oauth/callback'; // Replace with your redirect URI +import ProviderModal from './lib/ProviderModal'; +import { CRM_PROVIDERS } from './helpers/utils'; function App() { + const [isModalOpen, setIsModalOpen] = useState(false); + const [providerId, setProviderId] = useState(""); + const [returnUrl, setReturnUrl] = useState(""); + const [projectId, setPojectId] = useState("1"); + const [userId, setUserId] = useState("10"); + const { open, isReady } = useOAuth({ - linkToken: 'ADD_GENERATED_LINK_TOKEN', // Replace with your link token - clientId: CLIENT_ID!, - scopes: SCOPES, - redirectUri: REDIRECT_URI, + //clientId: CLIENT_ID!, not needed as we manage it for now + providerName: providerId, + returnUrl: returnUrl, //CLIENT WOULD PROVIDER IT + projectId: projectId, + linkedUserId: userId, //CLIENT WOULD PROVIDER IT onSuccess: () => console.log('OAuth successful') }); + const handleProviderSelection = (providerName: string) => { + console.log(`Selected provider: ${providerName}`); + switch (providerName) { + case CRM_PROVIDERS.HUBSPOT: + setProviderId("hubspot"); + //TODO: handle scopes + break; + + case CRM_PROVIDERS.PIPEDRIVE: + setProviderId("pipedrive"); + //TODO: handle scopes + break; + + case CRM_PROVIDERS.ZENDESK: + setProviderId("zendesk"); + //TODO: handle scopes + break; + + case CRM_PROVIDERS.ZOHO: + setProviderId("zoho"); + //TODO: handle scopes + break; + + case CRM_PROVIDERS.FRESHSALES: + setProviderId("freshsales"); + //TODO: handle scopes + break; + + default: + break; + + } + open(); + setIsModalOpen(false); + }; return ( <>

Integrations Flow

- + setIsModalOpen(false)} + />
) diff --git a/packages/frontend-snippet/src/helpers/utils.ts b/packages/frontend-snippet/src/helpers/utils.ts new file mode 100644 index 000000000..949d4585b --- /dev/null +++ b/packages/frontend-snippet/src/helpers/utils.ts @@ -0,0 +1,42 @@ +export enum CRM_PROVIDERS { + ZOHO = 'zoho', + ZENDESK = 'zendesk', + HUBSPOT = 'hubspot', + PIPEDRIVE = 'pipedrive', + FRESHSALES = 'freshsales', +} + +export const providersConfig = { + 'CRM': { + 'hubspot': { + clientId: 'ba591170-a7c7-4fca-8086-1bd178c6b14d', + scopes: 'crm.objects.contacts.read crm.objects.contacts.write' + }, + // TODO + 'zoho': { + clientId: 'Zoho_Client_Id', + scopes: 'Zoho_Scope' + }, + 'pipedrive': { + clientId: 'Pipedrive_Client_Id', + scopes: 'Pipedrive_Scope' + }, + 'freshsales': { + clientId: 'Pipedrive_Client_Id', + scopes: 'Pipedrive_Scope' + }, + 'zendesk': { + clientId: 'Pipedrive_Client_Id', + scopes: 'Pipedrive_Scope' + }, + + } +}; + +export const providerAuthBaseUrls = { + 'hubspot': `https://app-eu1.hubspot.com/oauth/authorize`, // TODO: HANDLE EU/US DOMAIN + 'zoho': `https://accounts.zoho.com/oauth/v2/auth`, + 'pipedrive': `https://oauth.pipedrive.com/oauth/authorize`, + 'freshsales': `https://some-freshsales-auth-url`, // Replace with actual URL + 'zendesk': `https://some-zendesk-auth-url`, // Replace with actual URL +}; \ No newline at end of file diff --git a/packages/frontend-snippet/src/hooks/useOAuth.ts b/packages/frontend-snippet/src/hooks/useOAuth.ts index 409c126cf..5436a015f 100644 --- a/packages/frontend-snippet/src/hooks/useOAuth.ts +++ b/packages/frontend-snippet/src/hooks/useOAuth.ts @@ -1,14 +1,17 @@ import { useState, useEffect } from 'react'; +import { providerAuthBaseUrls, providersConfig } from '../helpers/utils'; type UseOAuthProps = { - linkToken: string; - clientId: string; // Your OAuth client ID - scopes: string; // The scopes you are requesting - redirectUri: string; // The redirect URI registered with the OAuth provider + clientId?: string; + providerName: string; // Name of the OAuth provider + returnUrl: string; // Return URL after OAuth flow + projectId: string; // Project ID + linkedUserId: string; // Linked User ID onSuccess: () => void; }; -const useOAuth = ({ linkToken, clientId, scopes, redirectUri, onSuccess }: UseOAuthProps) => { + +const useOAuth = ({ providerName, returnUrl, projectId, linkedUserId, onSuccess }: UseOAuthProps) => { const [isReady, setIsReady] = useState(false); useEffect(() => { @@ -16,9 +19,29 @@ const useOAuth = ({ linkToken, clientId, scopes, redirectUri, onSuccess }: UseOA setTimeout(() => setIsReady(true), 1000); // Simulating async operation }, []); + const constructAuthUrl = () => { + const encodedRedirectUrl = encodeURIComponent(`http://localhost:3000/oauth/callback`); + const state = encodeURIComponent(JSON.stringify({ projectId, linkedUserId, providerName, returnUrl })); + + const vertical = 'CRM'; //TODO when multiple verticals + + const config = providersConfig[vertical][providerName]; + if (!config) { + throw new Error(`Unsupported provider: ${providerName}`); + } + + const { clientId, scopes } = config; + + const baseUrl = providerAuthBaseUrls[providerName]; + if (!baseUrl) { + throw new Error(`Unsupported provider: ${providerName}`); + } + + return `${baseUrl}?client_id=${encodeURIComponent(clientId)}&redirect_uri=${encodedRedirectUrl}&scope=${encodeURIComponent(scopes)}&state=${state}`; + }; + const openModal = () => { - //const authUrl = `https://app-eu1.hubspot.com/oauth/authorize?client_id=${encodeURIComponent(clientId)}&scope=${encodeURIComponent(scopes)}&redirect_uri=${encodeURIComponent(redirectUri)}`; - const authUrl = `https://app-eu1.hubspot.com/oauth/authorize?client_id=${clientId}&redirect_uri=http://localhost:3000/oauth/callback&scope=crm.lists.read%20crm.lists.write` + const authUrl = constructAuthUrl(); const width = 600, height = 600; const left = (window.innerWidth - width) / 2; const top = (window.innerHeight - height) / 2; diff --git a/packages/frontend-snippet/src/index.css b/packages/frontend-snippet/src/index.css index 2c3fac689..769116b79 100644 --- a/packages/frontend-snippet/src/index.css +++ b/packages/frontend-snippet/src/index.css @@ -67,3 +67,27 @@ button:focus-visible { background-color: #f9f9f9; } } + +.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; +} diff --git a/packages/frontend-snippet/src/lib/ProviderModal.tsx b/packages/frontend-snippet/src/lib/ProviderModal.tsx new file mode 100644 index 000000000..893790d50 --- /dev/null +++ b/packages/frontend-snippet/src/lib/ProviderModal.tsx @@ -0,0 +1,29 @@ +import React from 'react'; + +type ProviderModalProps = { + isOpen: boolean; + onSelectProvider: (providerName: string) => void; + onClose: () => void; +}; + +const PROVIDERS = ['hubspot', 'zoho', 'pipedrive', 'freshsales', 'zendesk']; + +const ProviderModal: React.FC = ({ isOpen, onSelectProvider, onClose }) => { + if (!isOpen) return null; + + return ( +
+
+

Select a CRM Provider

+ {PROVIDERS.map(provider => ( + + ))} + +
+
+ ); +}; + +export default ProviderModal; From c691dac7e1e091122767bcf1481a84c6aed2b920 Mon Sep 17 00:00:00 2001 From: nael Date: Mon, 13 Nov 2023 17:33:01 +0100 Subject: [PATCH 3/7] feat: frontend almost done --- packages/api/.env.example | 2 +- packages/api/package.json | 1 + .../connections/connections.controller.ts | 67 +++++----- .../@core/connections/connections.module.ts | 1 + .../crm/services/hubspot/hubspot.service.ts | 67 ++++++++-- .../api/src/@core/sentry/sentry.module.ts | 2 +- packages/api/src/@core/utils/config.ts | 2 +- packages/api/src/app.service.ts | 1 + packages/frontend-snippet/package.json | 3 + packages/frontend-snippet/postcss.config.js | 6 + .../public/assets/crm/freshsales_logo.webp | Bin 0 -> 5038 bytes .../public/assets/crm/hubspot_logo.png | Bin 0 -> 18200 bytes .../public/assets/crm/pipedrive_logo.jpeg | Bin 0 -> 6433 bytes .../public/assets/crm/zendesk_logo.png | Bin 0 -> 8267 bytes .../public/assets/crm/zoho_logo.png | Bin 0 -> 3607 bytes .../{src => public}/assets/react.svg | 0 packages/frontend-snippet/src/App.tsx | 6 +- .../frontend-snippet/src/helpers/utils.ts | 64 ++++++++-- .../frontend-snippet/src/hooks/useOAuth.ts | 6 +- packages/frontend-snippet/src/index.css | 13 +- .../src/lib/ProviderModal.tsx | 43 ++++--- packages/frontend-snippet/tailwind.config.js | 12 ++ pnpm-lock.yaml | 119 ++++++++++++++++-- 23 files changed, 319 insertions(+), 96 deletions(-) create mode 100644 packages/frontend-snippet/postcss.config.js create mode 100644 packages/frontend-snippet/public/assets/crm/freshsales_logo.webp create mode 100644 packages/frontend-snippet/public/assets/crm/hubspot_logo.png create mode 100644 packages/frontend-snippet/public/assets/crm/pipedrive_logo.jpeg create mode 100644 packages/frontend-snippet/public/assets/crm/zendesk_logo.png create mode 100644 packages/frontend-snippet/public/assets/crm/zoho_logo.png rename packages/frontend-snippet/{src => public}/assets/react.svg (100%) create mode 100644 packages/frontend-snippet/tailwind.config.js diff --git a/packages/api/.env.example b/packages/api/.env.example index 48540c9b6..27a66b2df 100644 --- a/packages/api/.env.example +++ b/packages/api/.env.example @@ -1,5 +1,5 @@ ENV=dev -PROD_DISTRIBUTION=managed # could be self-host if you want to run it locally +DISTRIBUTION=managed # could be self-host if you want to run it locally DATABASE_URL= JWT_SECRET="SECRET" POSTGRES_HOST= diff --git a/packages/api/package.json b/packages/api/package.json index fd44eb43b..53f108070 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -44,6 +44,7 @@ "passport-jwt": "^4.0.1", "passport-local": "^1.0.0", "pino-pretty": "^10.2.3", + "qs": "^6.11.2", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1", "uuid": "^9.0.1" diff --git a/packages/api/src/@core/connections/connections.controller.ts b/packages/api/src/@core/connections/connections.controller.ts index d62418cf7..692803f96 100644 --- a/packages/api/src/@core/connections/connections.controller.ts +++ b/packages/api/src/@core/connections/connections.controller.ts @@ -2,47 +2,52 @@ import { Controller, Get, Query, Res } from '@nestjs/common'; import { Response } from 'express'; // Importing the Express Response type for type checking import { CrmConnectionsService } from './crm/services/crm-connection.service'; import { ProviderVertical, getProviderVertical } from '../utils/providers'; +import { LoggerService } from '../logger/logger.service'; @Controller('connections') export class ConnectionsController { constructor(private readonly crmConnectionsService: CrmConnectionsService) {} - @Get('oauth/callback') handleCallback( @Res() res: Response, - @Query('projectId') projectId: string, - @Query('linkedUserId') linkedUserId: string, - @Query('providerName') providerName: string, + @Query('state') state: string, @Query('code') code: string, - @Query('returnUrl') returnUrl: string, @Query('accountURL') zohoAccountURL?: string, ) { - //TODO; ADD VERIFICATION OF PARAMS - switch (getProviderVertical(providerName)) { - case ProviderVertical.CRM: - this.crmConnectionsService.handleCRMCallBack( - projectId, - linkedUserId, - providerName, - code, - zohoAccountURL, - ); - case ProviderVertical.ATS: - break; - case ProviderVertical.Accounting: - break; - case ProviderVertical.FileStorage: - break; - case ProviderVertical.HRIS: - break; - case ProviderVertical.MarketingAutomation: - break; - case ProviderVertical.Ticketing: - break; - case ProviderVertical.Unknown: - break; - } + try { + if (!state || !code) throw new Error('no params found'); + const stateData = JSON.parse(decodeURIComponent(state)); + const { projectId, linkedUserId, providerName, returnUrl } = stateData; + //TODO; ADD VERIFICATION OF PARAMS + switch (getProviderVertical(providerName)) { + case ProviderVertical.CRM: + const zohoAccount = zohoAccountURL ? zohoAccountURL : ''; + this.crmConnectionsService.handleCRMCallBack( + projectId, + linkedUserId, + providerName, + code, + zohoAccount, + ); + case ProviderVertical.ATS: + break; + case ProviderVertical.Accounting: + break; + case ProviderVertical.FileStorage: + break; + case ProviderVertical.HRIS: + break; + case ProviderVertical.MarketingAutomation: + break; + case ProviderVertical.Ticketing: + break; + case ProviderVertical.Unknown: + break; + } - res.redirect(returnUrl); + res.redirect(returnUrl); + } catch (error) { + console.error('An error occurred', error.response?.data || error.message); + } } } diff --git a/packages/api/src/@core/connections/connections.module.ts b/packages/api/src/@core/connections/connections.module.ts index da2fd4f3c..563363bcf 100644 --- a/packages/api/src/@core/connections/connections.module.ts +++ b/packages/api/src/@core/connections/connections.module.ts @@ -1,6 +1,7 @@ import { Module } from '@nestjs/common'; import { CrmConnectionModule } from './crm/crm-connection.module'; import { ConnectionsController } from './connections.controller'; +import { LoggerService } from '../logger/logger.service'; @Module({ controllers: [ConnectionsController], 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 3a56b1af9..b16130ba1 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 @@ -5,12 +5,46 @@ import config from 'src/@core/utils/config'; import { Prisma } from '@prisma/client'; import { HubspotOAuthResponse } from '../../types'; import { LoggerService } from 'src/@core/logger/logger.service'; +import qs from 'qs'; @Injectable() export class HubspotConnectionService { constructor(private prisma: PrismaService, private logger: LoggerService) { this.logger.setContext(HubspotConnectionService.name); } + async addLinkedUserAndProjectTest() { + // Adding a new organization + const newOrganization = { + name: 'New Organization', + stripe_customer_id: 'stripe-customer-123', + }; + + const org = await this.prisma.organizations.create({ + data: newOrganization, + }); + this.logger.log('Added new organisation ' + org); + + // Example data for a new project + const newProject = { + name: 'New Project', + id_organization: 1n, // bigint value + }; + const data1 = await this.prisma.projects.create({ + data: newProject, + }); + this.logger.log('Added new project ' + data1); + + const newLinkedUser = { + linked_user_origin_id: '12345', + alias: 'ACME COMPANY', + status: 'Active', + id_project: 1n, // bigint value + }; + const data = await this.prisma.linked_users.create({ + data: newLinkedUser, + }); + this.logger.log('Added new linked_user ' + data); + } async handleHubspotCallback( linkedUserId: string, @@ -18,20 +52,35 @@ export class HubspotConnectionService { code: string, ) { try { - //first create a linked_user + //TMP STEP = first create a linked_user and a project id + //await this.addLinkedUserAndProjectTest(); + const newLinkedUser = { + linked_user_origin_id: '12345', + alias: 'ACME COMPANY', + status: 'Active', + id_project: 1n, // bigint value + }; + const data_ = await this.prisma.linked_users.create({ + data: newLinkedUser, + }); + this.logger.log('Added new linked_user ' + data_); //reconstruct the redirect URI that was passed in the frontend it must be the same - const REDIRECT_URI = `${config.OAUTH_REDIRECT_BASE}/oauth/crm/callback`; //tocheck - - const formData = { + const REDIRECT_URI = `${config.OAUTH_REDIRECT_BASE}/connections/oauth/callback`; //tocheck + const formData = new URLSearchParams({ grant_type: 'authorization_code', client_id: config.HUBSPOT_CLIENT_ID, client_secret: config.HUBSPOT_CLIENT_SECRET, redirect_uri: REDIRECT_URI, code: code, - }; + }); const res = await axios.post( 'https://api.hubapi.com/oauth/v1/token', - formData, + formData.toString(), + { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', + }, + }, ); const data: HubspotOAuthResponse = res.data; //console.log('OAuth credentials : hubspot ', data); @@ -58,6 +107,7 @@ export class HubspotConnectionService { // without it, we cant retrieve the right row in our db }, }); + this.logger.log('Successfully added tokens inside DB ' + db_res); } catch (error) { if (axios.isAxiosError(error)) { // Handle Axios-specific errors @@ -68,7 +118,10 @@ export class HubspotConnectionService { //console.error('Error with Prisma request:', error); this.logger.error('Error with Prisma request:', error.message); } - this.logger.error('An error occurred', error); + this.logger.error( + 'An error occurred...', + error.response?.data || error.message, + ); } } async handleHubspotTokenRefresh(connectionId: bigint, refresh_token: string) { diff --git a/packages/api/src/@core/sentry/sentry.module.ts b/packages/api/src/@core/sentry/sentry.module.ts index 89c08392a..cefcd440c 100644 --- a/packages/api/src/@core/sentry/sentry.module.ts +++ b/packages/api/src/@core/sentry/sentry.module.ts @@ -10,7 +10,7 @@ export class SentryModule { static forRoot(): DynamicModule { const isProduction = config.NODE_ENV === 'production'; const sentry_dsn = config.SENTRY_DSN; - const distribution = config.PROD_DISTRIBUTION; + const distribution = config.DISTRIBUTION; //enable sentry only if we are in production environment and if the product is managed by Panora if (isProduction && sentry_dsn && distribution == 'managed') { diff --git a/packages/api/src/@core/utils/config.ts b/packages/api/src/@core/utils/config.ts index 61e6f8fda..427922672 100644 --- a/packages/api/src/@core/utils/config.ts +++ b/packages/api/src/@core/utils/config.ts @@ -12,7 +12,7 @@ const config = { OAUTH_REDIRECT_BASE: process.env.OAUTH_REDIRECT_BASE, SENTRY_DSN: process.env.SENTRY_DSN, NODE_ENV: process.env.ENV, - PROD_DISTRIBUTION: process.env.PROD_DISTRIBUTION, + DISTRIBUTION: process.env.DISTRIBUTION, }; export default config; diff --git a/packages/api/src/app.service.ts b/packages/api/src/app.service.ts index 927d7cca0..dd0b21049 100644 --- a/packages/api/src/app.service.ts +++ b/packages/api/src/app.service.ts @@ -1,4 +1,5 @@ import { Injectable } from '@nestjs/common'; +import { LoggerService } from './@core/logger/logger.service'; @Injectable() export class AppService { diff --git a/packages/frontend-snippet/package.json b/packages/frontend-snippet/package.json index 3716a6d8c..84cda9730 100644 --- a/packages/frontend-snippet/package.json +++ b/packages/frontend-snippet/package.json @@ -19,9 +19,12 @@ "@typescript-eslint/eslint-plugin": "^6.0.0", "@typescript-eslint/parser": "^6.0.0", "@vitejs/plugin-react": "^4.0.3", + "autoprefixer": "^10.4.16", "eslint": "^8.45.0", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.3", + "postcss": "^8.4.31", + "tailwindcss": "^3.3.5", "typescript": "^5.0.2", "vite": "^4.4.5" } diff --git a/packages/frontend-snippet/postcss.config.js b/packages/frontend-snippet/postcss.config.js new file mode 100644 index 000000000..2e7af2b7f --- /dev/null +++ b/packages/frontend-snippet/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/packages/frontend-snippet/public/assets/crm/freshsales_logo.webp b/packages/frontend-snippet/public/assets/crm/freshsales_logo.webp new file mode 100644 index 0000000000000000000000000000000000000000..24444c155d2403f8c6a5cb3b2741fb25834bac4c GIT binary patch literal 5038 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 P00000NkvXXu0mjf+F=G) literal 0 HcmV?d00001 diff --git a/packages/frontend-snippet/public/assets/crm/pipedrive_logo.jpeg b/packages/frontend-snippet/public/assets/crm/pipedrive_logo.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..b544e91bef0c5b66dfaaa8e63eef44f311783b1c GIT binary patch literal 6433 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 MS?7AeHS@s#0C&hei~s-t literal 0 HcmV?d00001 diff --git a/packages/frontend-snippet/public/assets/crm/zoho_logo.png b/packages/frontend-snippet/public/assets/crm/zoho_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..c163d383b0aa2ed6b855be8c4f8bd36db4840f77 GIT binary patch literal 3607 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 literal 0 HcmV?d00001 diff --git a/packages/frontend-snippet/src/assets/react.svg b/packages/frontend-snippet/public/assets/react.svg similarity index 100% rename from packages/frontend-snippet/src/assets/react.svg rename to packages/frontend-snippet/public/assets/react.svg diff --git a/packages/frontend-snippet/src/App.tsx b/packages/frontend-snippet/src/App.tsx index c98d99134..d82e4636b 100644 --- a/packages/frontend-snippet/src/App.tsx +++ b/packages/frontend-snippet/src/App.tsx @@ -7,9 +7,9 @@ import { CRM_PROVIDERS } from './helpers/utils'; function App() { const [isModalOpen, setIsModalOpen] = useState(false); const [providerId, setProviderId] = useState(""); - const [returnUrl, setReturnUrl] = useState(""); + const [returnUrl, setReturnUrl] = useState("http://127.0.0.1:5173/"); const [projectId, setPojectId] = useState("1"); - const [userId, setUserId] = useState("10"); + const [userId, setUserId] = useState("1"); const { open, isReady } = useOAuth({ //clientId: CLIENT_ID!, not needed as we manage it for now @@ -56,7 +56,7 @@ function App() { }; return ( <> -

Integrations Flow

+

Integrations Flow

- ))} - +
+
+
+
Select CRM Provider
+ +
+
+ {PROVIDERS.map(provider => ( + + ))} +
); diff --git a/packages/frontend-snippet/tailwind.config.js b/packages/frontend-snippet/tailwind.config.js new file mode 100644 index 000000000..d37737fc0 --- /dev/null +++ b/packages/frontend-snippet/tailwind.config.js @@ -0,0 +1,12 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: [ + "./index.html", + "./src/**/*.{js,ts,jsx,tsx}", + ], + theme: { + extend: {}, + }, + plugins: [], +} + diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cfcd6216e..cc38f0025 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -89,6 +89,9 @@ importers: pino-pretty: specifier: ^10.2.3 version: 10.2.3 + qs: + specifier: ^6.11.2 + version: 6.11.2 reflect-metadata: specifier: ^0.1.13 version: 0.1.13 @@ -196,6 +199,9 @@ importers: '@vitejs/plugin-react': specifier: ^4.0.3 version: 4.0.3(vite@4.4.5) + autoprefixer: + specifier: ^10.4.16 + version: 10.4.16(postcss@8.4.31) eslint: specifier: ^8.45.0 version: 8.52.0 @@ -205,6 +211,12 @@ importers: eslint-plugin-react-refresh: specifier: ^0.4.3 version: 0.4.3(eslint@8.52.0) + postcss: + specifier: ^8.4.31 + version: 8.4.31 + tailwindcss: + specifier: ^3.3.5 + version: 3.3.5 typescript: specifier: ^5.0.2 version: 5.1.3 @@ -424,6 +436,11 @@ packages: '@algolia/requester-common': 4.20.0 dev: false + /@alloc/quick-lru@5.2.0: + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + dev: true + /@ampproject/remapping@2.2.1: resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==} engines: {node: '>=6.0.0'} @@ -5223,7 +5240,6 @@ packages: /arg@5.0.2: resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} - dev: false /argparse@1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} @@ -5347,7 +5363,6 @@ packages: picocolors: 1.0.0 postcss: 8.4.31 postcss-value-parser: 4.2.0 - dev: false /available-typed-arrays@1.0.5: resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} @@ -5779,7 +5794,6 @@ packages: /camelcase-css@2.0.1: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} - dev: false /camelcase-keys@6.2.2: resolution: {integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==} @@ -6463,7 +6477,6 @@ packages: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} engines: {node: '>=4'} hasBin: true - dev: false /cssnano-preset-advanced@5.3.10(postcss@8.4.31): resolution: {integrity: sha512-fnYJyCS9jgMU+cmHO1rPSPf9axbQyD7iUhLO5Df6O4G+fKIOMps+ZbU0PdGFejFBBZ3Pftf18fn1eG7MAPUSWQ==} @@ -6762,6 +6775,10 @@ packages: wrappy: 1.0.2 dev: true + /didyoumean@1.2.2: + resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + dev: true + /diff-sequences@29.6.3: resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -6778,6 +6795,10 @@ packages: dependencies: path-type: 4.0.0 + /dlv@1.1.3: + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + dev: true + /dns-equal@1.0.0: resolution: {integrity: sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==} dev: false @@ -7727,7 +7748,6 @@ packages: /fraction.js@4.3.7: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} - dev: false /fresh@0.5.2: resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} @@ -9380,7 +9400,6 @@ packages: /jiti@1.21.0: resolution: {integrity: sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==} hasBin: true - dev: false /joi@17.11.0: resolution: {integrity: sha512-NgB+lZLNoqISVy1rZocE9PZI36bL/77ie924Ri43yEvi9GUUMPeyVIr8KdFTMUlby1p0PBYMk9spIxEUQYqrJQ==} @@ -10226,7 +10245,6 @@ packages: /normalize-range@0.1.2: resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} engines: {node: '>=0.10.0'} - dev: false /normalize-url@4.5.1: resolution: {integrity: sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==} @@ -10274,6 +10292,11 @@ packages: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} + /object-hash@3.0.0: + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} + engines: {node: '>= 6'} + dev: true + /object-inspect@1.13.1: resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==} @@ -10669,6 +10692,11 @@ packages: hasBin: true dev: true + /pify@2.3.0: + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} + engines: {node: '>=0.10.0'} + dev: true + /pify@4.0.1: resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} engines: {node: '>=6'} @@ -10834,7 +10862,29 @@ packages: postcss-selector-parser: 6.0.13 dev: false - /postcss-load-config@4.0.1: + /postcss-import@15.1.0(postcss@8.4.31): + resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} + engines: {node: '>=14.0.0'} + peerDependencies: + postcss: ^8.0.0 + dependencies: + postcss: 8.4.31 + postcss-value-parser: 4.2.0 + read-cache: 1.0.0 + resolve: 1.22.8 + dev: true + + /postcss-js@4.0.1(postcss@8.4.31): + resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} + engines: {node: ^12 || ^14 || >= 16} + peerDependencies: + postcss: ^8.4.21 + dependencies: + camelcase-css: 2.0.1 + postcss: 8.4.31 + dev: true + + /postcss-load-config@4.0.1(postcss@8.4.31): resolution: {integrity: sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==} engines: {node: '>= 14'} peerDependencies: @@ -10847,6 +10897,7 @@ packages: optional: true dependencies: lilconfig: 2.1.0 + postcss: 8.4.31 yaml: 2.3.3 dev: true @@ -10986,6 +11037,16 @@ packages: postcss: 8.4.31 dev: false + /postcss-nested@6.0.1(postcss@8.4.31): + resolution: {integrity: sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.2.14 + dependencies: + postcss: 8.4.31 + postcss-selector-parser: 6.0.13 + dev: true + /postcss-normalize-charset@5.1.0(postcss@8.4.31): resolution: {integrity: sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==} engines: {node: ^10 || ^12 || >=14.0} @@ -11125,7 +11186,6 @@ packages: dependencies: cssesc: 3.0.0 util-deprecate: 1.0.2 - dev: false /postcss-sort-media-queries@4.4.1(postcss@8.4.31): resolution: {integrity: sha512-QDESFzDDGKgpiIh4GYXsSy6sek2yAwQx1JASl5AxBtU1Lq2JfKBljIPNdil989NcSKRQX1ToiaKphImtBuhXWw==} @@ -11160,7 +11220,6 @@ packages: /postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - dev: false /postcss-zindex@5.1.0(postcss@8.4.31): resolution: {integrity: sha512-fgFMf0OtVSBR1va1JNHYgMxYk73yhn/qb4uQDq1DLGYolz8gHCyr/sesEuGUaYs58E3ZJRcpoGuPVoB7Meiq9A==} @@ -11345,7 +11404,6 @@ packages: engines: {node: '>=0.6'} dependencies: side-channel: 1.0.4 - dev: true /queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -11615,6 +11673,12 @@ packages: loose-envify: 1.4.0 dev: false + /read-cache@1.0.0: + resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} + dependencies: + pify: 2.3.0 + dev: true + /read-pkg-up@7.0.1: resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} engines: {node: '>=8'} @@ -12753,6 +12817,37 @@ packages: engines: {node: '>=0.10'} dev: true + /tailwindcss@3.3.5: + resolution: {integrity: sha512-5SEZU4J7pxZgSkv7FP1zY8i2TIAOooNZ1e/OGtxIEv6GltpoiXUqWvLy89+a10qYTB1N5Ifkuw9lqQkN9sscvA==} + engines: {node: '>=14.0.0'} + hasBin: true + dependencies: + '@alloc/quick-lru': 5.2.0 + arg: 5.0.2 + chokidar: 3.5.3 + didyoumean: 1.2.2 + dlv: 1.1.3 + fast-glob: 3.3.1 + glob-parent: 6.0.2 + is-glob: 4.0.3 + jiti: 1.21.0 + lilconfig: 2.1.0 + micromatch: 4.0.5 + normalize-path: 3.0.0 + object-hash: 3.0.0 + picocolors: 1.0.0 + postcss: 8.4.31 + postcss-import: 15.1.0(postcss@8.4.31) + postcss-js: 4.0.1(postcss@8.4.31) + postcss-load-config: 4.0.1(postcss@8.4.31) + postcss-nested: 6.0.1(postcss@8.4.31) + postcss-selector-parser: 6.0.13 + resolve: 1.22.8 + sucrase: 3.34.0 + transitivePeerDependencies: + - ts-node + dev: true + /tapable@1.1.3: resolution: {integrity: sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==} engines: {node: '>=6'} @@ -13103,7 +13198,7 @@ packages: execa: 5.1.1 globby: 11.1.0 joycon: 3.1.1 - postcss-load-config: 4.0.1 + postcss-load-config: 4.0.1(postcss@8.4.31) resolve-from: 5.0.0 rollup: 3.29.4 source-map: 0.8.0-beta.0 From 4492d2d49ed0292ab8f08a82c0a48cadf864994c Mon Sep 17 00:00:00 2001 From: nael Date: Mon, 13 Nov 2023 17:42:08 +0100 Subject: [PATCH 4/7] feat: fix build --- packages/frontend-snippet/.eslintrc.cjs | 1 + packages/frontend-snippet/package.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/frontend-snippet/.eslintrc.cjs b/packages/frontend-snippet/.eslintrc.cjs index d6c953795..5701e8c3d 100644 --- a/packages/frontend-snippet/.eslintrc.cjs +++ b/packages/frontend-snippet/.eslintrc.cjs @@ -14,5 +14,6 @@ module.exports = { 'warn', { allowConstantExport: true }, ], + '@typescript-eslint/no-unused-vars': 'off', }, } diff --git a/packages/frontend-snippet/package.json b/packages/frontend-snippet/package.json index 84cda9730..91a6d618a 100644 --- a/packages/frontend-snippet/package.json +++ b/packages/frontend-snippet/package.json @@ -1,5 +1,5 @@ { - "name": "frontend-snippet", + "name": "panora-react-snippet", "private": true, "version": "0.0.0", "type": "module", From e622d86568097255e81c4636546e1bd695211117 Mon Sep 17 00:00:00 2001 From: nael Date: Mon, 13 Nov 2023 17:52:31 +0100 Subject: [PATCH 5/7] feat: fix build api --- .../crm/services/hubspot/hubspot.service.ts | 25 ++++++++----------- 1 file changed, 10 insertions(+), 15 deletions(-) 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 b16130ba1..db58f83a7 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 @@ -53,17 +53,7 @@ export class HubspotConnectionService { ) { try { //TMP STEP = first create a linked_user and a project id - //await this.addLinkedUserAndProjectTest(); - const newLinkedUser = { - linked_user_origin_id: '12345', - alias: 'ACME COMPANY', - status: 'Active', - id_project: 1n, // bigint value - }; - const data_ = await this.prisma.linked_users.create({ - data: newLinkedUser, - }); - this.logger.log('Added new linked_user ' + data_); + await this.addLinkedUserAndProjectTest(); //reconstruct the redirect URI that was passed in the frontend it must be the same const REDIRECT_URI = `${config.OAUTH_REDIRECT_BASE}/connections/oauth/callback`; //tocheck const formData = new URLSearchParams({ @@ -126,18 +116,23 @@ export class HubspotConnectionService { } async handleHubspotTokenRefresh(connectionId: bigint, refresh_token: string) { try { - const REDIRECT_URI = `${config.OAUTH_REDIRECT_BASE}/oauth/crm/callback`; + const REDIRECT_URI = `${config.OAUTH_REDIRECT_BASE}/connections/oauth/callback`; //tocheck - const formData = { + const formData = new URLSearchParams({ grant_type: 'refresh_token', client_id: config.HUBSPOT_CLIENT_ID, client_secret: config.HUBSPOT_CLIENT_SECRET, redirect_uri: REDIRECT_URI, refresh_token: refresh_token, - }; + }); const res = await axios.post( 'https://api.hubapi.com/oauth/v1/token', - formData, + formData.toString(), + { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', + }, + }, ); const data: HubspotOAuthResponse = res.data; await this.prisma.connections.update({ From 19d51492d8989a6535f73e7ad7c5b82dfa54924a Mon Sep 17 00:00:00 2001 From: nael Date: Mon, 13 Nov 2023 17:56:18 +0100 Subject: [PATCH 6/7] feat: added fix for frontend --- packages/frontend-snippet/src/lib/ProviderModal.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/frontend-snippet/src/lib/ProviderModal.tsx b/packages/frontend-snippet/src/lib/ProviderModal.tsx index d3288efb0..bb5653e55 100644 --- a/packages/frontend-snippet/src/lib/ProviderModal.tsx +++ b/packages/frontend-snippet/src/lib/ProviderModal.tsx @@ -1,8 +1,13 @@ import { providersArray } from "../helpers/utils"; +// Assuming you have these types defined +type ProviderModalProps = { + isOpen: boolean; + onSelectProvider: (providerName: string) => void; + onClose: () => void; +}; - -const ProviderModal = ({ isOpen, onSelectProvider, onClose }) => { +const ProviderModal = ({ isOpen, onSelectProvider, onClose }: ProviderModalProps) => { if (!isOpen) {console.log("null modal"); return null} const PROVIDERS = providersArray('CRM'); return ( From 27d200c6cbd28e2a27983f7bfc6a5375068afd07 Mon Sep 17 00:00:00 2001 From: nael Date: Mon, 13 Nov 2023 17:59:53 +0100 Subject: [PATCH 7/7] feat: fix build release --- packages/frontend-snippet/src/hooks/useOAuth.ts | 2 +- packages/frontend-snippet/tsconfig.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/frontend-snippet/src/hooks/useOAuth.ts b/packages/frontend-snippet/src/hooks/useOAuth.ts index 71c3bff70..f23e56729 100644 --- a/packages/frontend-snippet/src/hooks/useOAuth.ts +++ b/packages/frontend-snippet/src/hooks/useOAuth.ts @@ -47,7 +47,7 @@ const useOAuth = ({ providerName, returnUrl, projectId, linkedUserId, onSuccess const top = (window.innerHeight - height) / 2; window.open(authUrl, 'OAuth', `width=${width},height=${height},top=${top},left=${left}`); - // Call the onSuccess function here if needed, or after the OAuth flow is completed + onSuccess(); }; return { open: openModal, isReady }; diff --git a/packages/frontend-snippet/tsconfig.json b/packages/frontend-snippet/tsconfig.json index a7fc6fbf2..e49f745a2 100644 --- a/packages/frontend-snippet/tsconfig.json +++ b/packages/frontend-snippet/tsconfig.json @@ -16,8 +16,8 @@ /* Linting */ "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, + "noUnusedLocals": false, + "noUnusedParameters": false, "noFallthroughCasesInSwitch": true }, "include": ["src"],