From 0e20e340c0c5210fd876d30b68e27eb626e83caa Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Thu, 6 Mar 2025 18:21:39 -0800 Subject: [PATCH] feat(oauth): modified google drive/gmail icons, added credentials selector sub-block to gmail tool --- .../components/sub-block/sub-block.tsx | 22 ++++++++ app/w/[id]/workflow.tsx | 21 +++++++ .../components/credentials/credentials.tsx | 26 +++++---- blocks/blocks/gmail.ts | 28 +++++++--- blocks/types.ts | 6 ++ components/icons.tsx | 55 +++++++++++++++++-- 6 files changed, 133 insertions(+), 25 deletions(-) diff --git a/app/w/[id]/components/workflow-block/components/sub-block/sub-block.tsx b/app/w/[id]/components/workflow-block/components/sub-block/sub-block.tsx index f0884389d3..93b7bda2b9 100644 --- a/app/w/[id]/components/workflow-block/components/sub-block/sub-block.tsx +++ b/app/w/[id]/components/workflow-block/components/sub-block/sub-block.tsx @@ -7,6 +7,7 @@ import { DateInput } from './components/date-input' import { Dropdown } from './components/dropdown' import { EvalInput } from './components/eval-input' import { LongInput } from './components/long-input' +import { OAuthInput } from './components/oauth-input' import { ShortInput } from './components/short-input' import { SliderInput } from './components/slider-input' import { Switch } from './components/switch' @@ -107,6 +108,27 @@ export function SubBlock({ blockId, config, isConnecting }: SubBlockProps) { return ( ) + case 'oauth-input': + return ( + { + // Use the workflow store to update the value + const event = new CustomEvent('update-subblock-value', { + detail: { + blockId, + subBlockId: config.id, + value, + }, + }) + window.dispatchEvent(event) + }} + provider={config.provider as any} + requiredScopes={config.requiredScopes || []} + label={config.placeholder} + serviceId={config.serviceId} + /> + ) default: return null } diff --git a/app/w/[id]/workflow.tsx b/app/w/[id]/workflow.tsx index 0aaa4bc72e..396eef89e7 100644 --- a/app/w/[id]/workflow.tsx +++ b/app/w/[id]/workflow.tsx @@ -18,6 +18,7 @@ import { useNotificationStore } from '@/stores/notifications/store' import { useGeneralStore } from '@/stores/settings/general/store' import { getSyncManagers, initializeSyncManagers, isSyncInitialized } from '@/stores/sync-registry' import { useWorkflowRegistry } from '@/stores/workflows/registry/store' +import { useSubBlockStore } from '@/stores/workflows/subblock/store' import { useWorkflowStore } from '@/stores/workflows/workflow/store' import { NotificationList } from '@/app/w/[id]/components/notifications/notifications' import { getBlock } from '../../../blocks' @@ -51,6 +52,7 @@ function WorkflowContent() { const { workflows, setActiveWorkflow, createWorkflow } = useWorkflowRegistry() const { blocks, edges, loops, addBlock, updateBlockPosition, addEdge, removeEdge } = useWorkflowStore() + const { setValue: setSubBlockValue } = useSubBlockStore() // Add OAuth error handling const { modalState, handleOAuthError, closeModal } = useOAuthErrorHandler() @@ -312,6 +314,25 @@ function WorkflowContent() { return () => window.removeEventListener('keydown', handleKeyDown) }, [selectedEdgeId, removeEdge]) + // Handle sub-block value updates from custom events + useEffect(() => { + const handleSubBlockValueUpdate = (event: CustomEvent) => { + const { blockId, subBlockId, value } = event.detail + if (blockId && subBlockId) { + setSubBlockValue(blockId, subBlockId, value) + } + } + + window.addEventListener('update-subblock-value', handleSubBlockValueUpdate as EventListener) + + return () => { + window.removeEventListener( + 'update-subblock-value', + handleSubBlockValueUpdate as EventListener + ) + } + }, [setSubBlockValue]) + if (!isInitialized) return null return ( diff --git a/app/w/components/sidebar/components/settings-modal/components/credentials/credentials.tsx b/app/w/components/sidebar/components/settings-modal/components/credentials/credentials.tsx index 23d822dc4c..8325b20b01 100644 --- a/app/w/components/sidebar/components/settings-modal/components/credentials/credentials.tsx +++ b/app/w/components/sidebar/components/settings-modal/components/credentials/credentials.tsx @@ -3,7 +3,7 @@ import { useEffect, useState } from 'react' import { useRouter, useSearchParams } from 'next/navigation' import { Check, ExternalLink, RefreshCw } from 'lucide-react' -import { GithubIcon, GoogleIcon, xIcon as XIcon } from '@/components/icons' +import { GithubIcon, GoogleDriveIcon, xIcon as XIcon } from '@/components/icons' import { GmailIcon } from '@/components/icons' import { Button } from '@/components/ui/button' import { Card } from '@/components/ui/card' @@ -61,7 +61,7 @@ export function Credentials({ onOpenChange }: CredentialsProps) { description: 'Streamline file organization and document workflows.', provider: 'google', providerId: 'google-drive', - icon: , + icon: , isConnected: false, scopes: [], }, @@ -276,6 +276,19 @@ export function Credentials({ onOpenChange }: CredentialsProps) {

+ {pendingService && ( +
+
+ +
+

+ Action Required: Please connect your + account to enable the requested features. The required service will be highlighted + below. +

+
+ )} +
{isLoading ? ( <> @@ -326,15 +339,6 @@ export function Credentials({ onOpenChange }: CredentialsProps) { )) )}
- - {pendingService && ( -
-

- Note: Connect this service to use tools that - require this authentication. -

-
- )} ) } diff --git a/blocks/blocks/gmail.ts b/blocks/blocks/gmail.ts index 4d327bfd1f..f83aa526da 100644 --- a/blocks/blocks/gmail.ts +++ b/blocks/blocks/gmail.ts @@ -9,7 +9,7 @@ export const GmailBlock: BlockConfig = { longDescription: 'Integrate Gmail functionality to send, read, and search email messages within your workflow. Automate email communications and process email content using OAuth authentication.', category: 'tools', - bgColor: '#F14537', + bgColor: '#E0E0E0', icon: GmailIcon, subBlocks: [ // Operation selector @@ -24,14 +24,19 @@ export const GmailBlock: BlockConfig = { { label: 'Search Emails', id: 'search_gmail' }, ], }, - // OAuth Token + // Gmail Credentials { - id: 'accessToken', - title: 'Access Token', - type: 'short-input', + id: 'credential', + title: 'Gmail Account', + type: 'oauth-input', layout: 'full', - placeholder: 'Enter Gmail OAuth token', - password: true, + provider: 'google', + serviceId: 'gmail', + requiredScopes: [ + 'https://www.googleapis.com/auth/gmail.send', + 'https://www.googleapis.com/auth/gmail.readonly', + ], + placeholder: 'Select Gmail account', }, // Send Email Fields { @@ -100,11 +105,18 @@ export const GmailBlock: BlockConfig = { throw new Error(`Invalid Gmail operation: ${params.operation}`) } }, + params: (params) => { + // Add the credential ID to the params + return { + ...params, + _credentialId: params.credential, + } + }, }, }, inputs: { operation: { type: 'string', required: true }, - accessToken: { type: 'string', required: true }, + credential: { type: 'string', required: true }, // Send operation inputs to: { type: 'string', required: false }, subject: { type: 'string', required: false }, diff --git a/blocks/types.ts b/blocks/types.ts index d848b93a0c..4f0c35a362 100644 --- a/blocks/types.ts +++ b/blocks/types.ts @@ -25,6 +25,7 @@ export type SubBlockType = | 'eval-input' // Evaluation input | 'date-input' // Date input | 'time-input' // Time input + | 'oauth-input' // OAuth credential selector // Component width setting export type SubBlockLayout = 'full' | 'half' @@ -95,6 +96,10 @@ export interface SubBlockConfig { value: string | number | boolean } } + // OAuth specific properties + provider?: string + serviceId?: string + requiredScopes?: string[] } // Main block definition @@ -111,6 +116,7 @@ export interface BlockConfig { access: string[] config?: { tool: (params: Record) => string + params?: (params: Record) => Record } } inputs: Record diff --git a/components/icons.tsx b/components/icons.tsx index 38a1f09b82..b033ba8074 100644 --- a/components/icons.tsx +++ b/components/icons.tsx @@ -1169,25 +1169,68 @@ export const NotionIcon = (props: SVGProps) => { export const GmailIcon = (props: SVGProps) => { return ( - - - + + + ) } +export const GoogleDriveIcon = (props: SVGProps) => { + return ( + + + + + + + + + ) +} + export const xAIIcon = (props: SVGProps) => { return (