Repository: harness/canary
Pull request #1250: Connectors Reference
Author: login: sans-harness
Created: 2025-03-13T18:55:34Z | Merged: 2025-03-20T01:18:47Z

- Adds the connector reference view


https://github.com/user-attachments/assets/3f32f8c5-708f-45d6-88ce-caef675e3485





Diff:
diff --git a/apps/design-system/src/pages/view-preview/view-preview.tsx b/apps/design-system/src/pages/view-preview/view-preview.tsx
index 423d78ecda..a97568f511 100644
--- a/apps/design-system/src/pages/view-preview/view-preview.tsx
+++ b/apps/design-system/src/pages/view-preview/view-preview.tsx
@@ -6,6 +6,7 @@ import { Navigate, Route, Routes } from 'react-router-dom'
 
 import { ProfileSettingsViewWrapper } from '@/pages/view-preview/profile-settings-view-wrapper.tsx'
 import { RepoSettingsViewWrapper } from '@/pages/view-preview/repo-settings-view-wrapper'
+import { ConnectorsPage } from '@subjects/views/connectors/connectors'
 import ExecutionListWrapper from '@subjects/views/execution-list/execution-list'
 import { LabelsForm } from '@subjects/views/labels/labels-form'
 import { ProjectLabelsList } from '@subjects/views/labels/project-labels-list'
@@ -496,6 +497,15 @@ export const viewPreviews: Record<string, ViewPreviewGroup> = {
       }
     }
   },
+  connectors: {
+    label: 'Connectors',
+    items: {
+      'connector-reference': {
+        label: 'Connector Reference',
+        element: <ConnectorsPage />
+      }
+    }
+  },
   project: {
     label: 'Project',
     items: {
diff --git a/apps/design-system/src/subjects/views/connectors/connectors.tsx b/apps/design-system/src/subjects/views/connectors/connectors.tsx
new file mode 100644
index 0000000000..2fc17a400c
--- /dev/null
+++ b/apps/design-system/src/subjects/views/connectors/connectors.tsx
@@ -0,0 +1,105 @@
+import { useState } from 'react'
+
+import { Button, Drawer, Spacer } from '@harnessio/ui/components'
+import { ConnectorHeader, ConnectorItem, ConnectorReference, ConnectorType, DirectionEnum } from '@harnessio/ui/views'
+
+import mockAccountsData from '../secrets/mock-account-data.json'
+import mockOrgData from '../secrets/mock-org-data.json'
+import mockProjectsData from '../secrets/mock-project-data.json'
+import { Scope, ScopeEnum, scopeHierarchy } from '../secrets/types'
+import mockConnectorsData from './mock-connectors-data.json'
+
+export const ConnectorsPage = () => {
+  const [selectedType, setSelectedType] = useState<ConnectorType>(ConnectorType.EXISTING)
+  const [, setActiveScope] = useState<Scope>(ScopeEnum.ORGANIZATION)
+
+  // State for existing connectors
+  const [selectedConnector, setSelectedConnector] = useState<ConnectorItem | null>(null)
+  const [parentFolder, setParentFolder] = useState<string | null>(mockAccountsData[0].accountName)
+  const [childFolder, setChildFolder] = useState<string | null>(mockProjectsData[0].projectResponse.project.identifier)
+
+  // Handlers for existing connectors
+  const handleSelectConnector = (connector: ConnectorItem) => {
+    setSelectedConnector(connector)
+    console.log('Selected connector:', connector)
+  }
+
+  const handleScopeChange = (direction: DirectionEnum) => {
+    setActiveScope(prevScope => {
+      const newScope =
+        direction === DirectionEnum.PARENT ? scopeHierarchy[prevScope].parent! : scopeHierarchy[prevScope].child!
+      switch (newScope) {
+        case ScopeEnum.ACCOUNT:
+          setParentFolder(null)
+          setChildFolder(mockOrgData[0].organizationResponse.organization.identifier)
+          break
+        case ScopeEnum.ORGANIZATION:
+          setParentFolder(mockAccountsData[0].accountName)
+          setChildFolder(mockProjectsData[0].projectResponse.project.identifier)
+          break
+        case ScopeEnum.PROJECT:
+          setParentFolder(mockOrgData[0].organizationResponse.organization.identifier)
+          setChildFolder(null)
+          break
+      }
+      return newScope
+    })
+  }
+
+  const handleCancel = () => {
+    console.log('Cancelled')
+  }
+
+  const renderConnectorContent = () => {
+    switch (selectedType) {
+      case ConnectorType.NEW:
+        return (
+          <div className="p-4">
+            <h2 className="text-xl font-semibold mb-4">Create New Connector</h2>
+            <p>Add form for new connector here</p>
+            {/* Render Calvin/Shaurya's create connector flow from here */}
+          </div>
+        )
+      case ConnectorType.EXISTING:
+        return (
+          <ConnectorReference
+            connectorsData={mockConnectorsData.map(connector => {
+              return {
+                ...connector,
+                name: connector.connector.name,
+                id: connector.connector.identifier
+              }
+            })}
+            parentFolder={parentFolder}
+            childFolder={childFolder}
+            selectedEntity={selectedConnector}
+            onSelectEntity={handleSelectConnector}
+            onScopeChange={handleScopeChange}
+            onCancel={handleCancel}
+            isLoading={false}
+            apiError="Could not fetch connectors, unauthorized"
+          />
+        )
+      default:
+        return null
+    }
+  }
+
+  return (
+    <Drawer.Root direction="right">
+      <Drawer.Trigger>
+        <Button>Add Connector</Button>
+      </Drawer.Trigger>
+      <Drawer.Content>
+        <Drawer.Header>
+          <Drawer.Title className="text-3xl">Connectors</Drawer.Title>
+        </Drawer.Header>
+        <Spacer size={5} />
+
+        <ConnectorHeader onChange={setSelectedType} selectedType={selectedType} />
+        <Spacer size={5} />
+        {renderConnectorContent()}
+      </Drawer.Content>
+    </Drawer.Root>
+  )
+}
diff --git a/apps/design-system/src/subjects/views/connectors/mock-connectors-data.json b/apps/design-system/src/subjects/views/connectors/mock-connectors-data.json
new file mode 100644
index 0000000000..a7f3d3ca7d
--- /dev/null
+++ b/apps/design-system/src/subjects/views/connectors/mock-connectors-data.json
@@ -0,0 +1,223 @@
+[
+  {
+    "connector": {
+      "name": "connector-1",
+      "identifier": "connector1",
+      "description": "",
+      "accountIdentifier": "account123",
+      "orgIdentifier": "org1",
+      "projectIdentifier": "project1",
+      "tags": {},
+      "type": "Github",
+      "spec": {
+        "url": "https://github.com/organization/repository",
+        "validationRepo": null,
+        "authentication": {
+          "type": "Http",
+          "spec": {
+            "type": "UsernameToken",
+            "spec": {
+              "username": "username1",
+              "usernameRef": null,
+              "tokenRef": "token1"
+            }
+          }
+        },
+        "apiAccess": {
+          "type": "Token",
+          "spec": {
+            "tokenRef": "token1"
+          }
+        },
+        "delegateSelectors": [],
+        "executeOnDelegate": false,
+        "proxy": false,
+        "ignoreTestConnection": false,
+        "type": "Repo"
+      }
+    },
+    "createdAt": 1719353669620,
+    "lastModifiedAt": 1719353669616,
+    "status": {
+      "status": "SUCCESS",
+      "errorSummary": null,
+      "errors": null,
+      "testedAt": 1719353671202,
+      "lastTestedAt": 0,
+      "lastConnectedAt": 1719353671202,
+      "lastAlertSent": null
+    },
+    "activityDetails": {
+      "lastActivityTime": 1719353669616
+    },
+    "harnessManaged": false,
+    "gitDetails": {
+      "objectId": null,
+      "branch": null,
+      "repoIdentifier": null,
+      "rootFolder": null,
+      "filePath": null,
+      "repoName": null,
+      "commitId": null,
+      "fileUrl": null,
+      "repoUrl": null,
+      "parentEntityConnectorRef": null,
+      "parentEntityRepoName": null,
+      "isHarnessCodeRepo": null
+    },
+    "entityValidityDetails": {
+      "valid": true,
+      "invalidYaml": null
+    },
+    "governanceMetadata": null,
+    "isFavorite": false
+  },
+  {
+    "connector": {
+      "name": "connector-2",
+      "identifier": "connector2",
+      "description": "",
+      "accountIdentifier": "account123",
+      "orgIdentifier": "org1",
+      "projectIdentifier": "project1",
+      "tags": {},
+      "type": "Github",
+      "spec": {
+        "url": "https://github.com/organization/repository2",
+        "validationRepo": null,
+        "authentication": {
+          "type": "Http",
+          "spec": {
+            "type": "UsernameToken",
+            "spec": {
+              "username": "username2",
+              "usernameRef": null,
+              "tokenRef": "token2"
+            }
+          }
+        },
+        "apiAccess": null,
+        "delegateSelectors": ["delegate-1"],
+        "executeOnDelegate": true,
+        "proxy": false,
+        "ignoreTestConnection": false,
+        "type": "Repo"
+      }
+    },
+    "createdAt": 1700007726035,
+    "lastModifiedAt": 1700007726032,
+    "status": {
+      "status": "FAILURE",
+      "errorSummary": "Error Encountered (Please ensure that the api access credentials are correct.)",
+      "errors": [
+        {
+          "reason": "Unexpected Error",
+          "message": "Please ensure that the api access credentials are correct.",
+          "code": 450
+        }
+      ],
+      "testedAt": 1700010337928,
+      "lastTestedAt": 0,
+      "lastConnectedAt": 0,
+      "lastAlertSent": null
+    },
+    "activityDetails": {
+      "lastActivityTime": 1700007726157
+    },
+    "harnessManaged": false,
+    "gitDetails": {
+      "objectId": null,
+      "branch": null,
+      "repoIdentifier": null,
+      "rootFolder": null,
+      "filePath": null,
+      "repoName": null,
+      "commitId": null,
+      "fileUrl": null,
+      "repoUrl": null,
+      "parentEntityConnectorRef": null,
+      "parentEntityRepoName": null,
+      "isHarnessCodeRepo": null
+    },
+    "entityValidityDetails": {
+      "valid": true,
+      "invalidYaml": null
+    },
+    "governanceMetadata": null,
+    "isFavorite": false
+  },
+  {
+    "connector": {
+      "name": "connector-3",
+      "identifier": "connector3",
+      "description": "",
+      "accountIdentifier": "account123",
+      "orgIdentifier": "org1",
+      "projectIdentifier": "project1",
+      "tags": {},
+      "type": "Github",
+      "spec": {
+        "url": "https://github.com/organization/repository3",
+        "validationRepo": null,
+        "authentication": {
+          "type": "Http",
+          "spec": {
+            "type": "UsernameToken",
+            "spec": {
+              "username": "username3",
+              "usernameRef": null,
+              "tokenRef": "token3"
+            }
+          }
+        },
+        "apiAccess": null,
+        "delegateSelectors": ["delegate-1"],
+        "executeOnDelegate": true,
+        "proxy": false,
+        "ignoreTestConnection": false,
+        "type": "Repo"
+      }
+    },
+    "createdAt": 1700005553911,
+    "lastModifiedAt": 1700005642847,
+    "status": {
+      "status": "FAILURE",
+      "errorSummary": "Error Encountered (There are no eligible delegates available in the account to execute the task.\n\nNon active delegates , Delegate(s) don't have selectors [delegate-1])",
+      "errors": [
+        {
+          "reason": "Unexpected Error",
+          "message": "There are no eligible delegates available in the account to execute the task.\n\nNon active delegates , Delegate(s) don't have selectors [delegate-1]",
+          "code": 450
+        }
+      ],
+      "testedAt": 1700007688566,
+      "lastTestedAt": 0,
+      "lastConnectedAt": 0,
+      "lastAlertSent": null
+    },
+    "activityDetails": {
+      "lastActivityTime": 1700005642977
+    },
+    "harnessManaged": false,
+    "gitDetails": {
+      "objectId": null,
+      "branch": null,
+      "repoIdentifier": null,
+      "rootFolder": null,
+      "filePath": null,
+      "repoName": null,
+      "commitId": null,
+      "fileUrl": null,
+      "repoUrl": null,
+      "parentEntityConnectorRef": null,
+      "parentEntityRepoName": null,
+      "isHarnessCodeRepo": null
+    },
+    "entityValidityDetails": {
+      "valid": true,
+      "invalidYaml": null
+    },
+    "governanceMetadata": null,
+    "isFavorite": false
+  }
+]
diff --git a/apps/design-system/src/subjects/views/secrets/secrets.tsx b/apps/design-system/src/subjects/views/secrets/secrets.tsx
index 4edeb89288..9a2a54e0a9 100644
--- a/apps/design-system/src/subjects/views/secrets/secrets.tsx
+++ b/apps/design-system/src/subjects/views/secrets/secrets.tsx
@@ -18,19 +18,13 @@ import mockAccountsData from './mock-account-data.json'
 import mockOrgData from './mock-org-data.json'
 import mockProjectsData from './mock-project-data.json'
 import mockSecretsData from './mock-secrets-data.json'
-import { ScopeEnum, SecretScope } from './types'
+import { Scope, ScopeEnum, scopeHierarchy } from './types'
 
 export const SecretsPage = () => {
-  const scopeHierarchy: Record<SecretScope, { parent: SecretScope | null; child: SecretScope | null }> = {
-    account: { parent: null, child: ScopeEnum.ORGANIZATION },
-    organization: { parent: ScopeEnum.ACCOUNT, child: ScopeEnum.PROJECT },
-    project: { parent: ScopeEnum.ORGANIZATION, child: null }
-  }
-
   const [selectedType, setSelectedType] = useState<SecretType>(SecretType.NEW)
 
   // State for existing secrets
-  const [, setActiveScope] = useState<SecretScope>(ScopeEnum.ORGANIZATION)
+  const [, setActiveScope] = useState<Scope>(ScopeEnum.ORGANIZATION)
   const [selectedSecret, setSelectedSecret] = useState<SecretItem | null>(null)
   const [parentFolder, setParentFolder] = useState<string | null>(mockAccountsData[0].accountName)
   const [childFolder, setChildFolder] = useState<string | null>(mockProjectsData[0].projectResponse.project.identifier)
diff --git a/apps/design-system/src/subjects/views/secrets/types.ts b/apps/design-system/src/subjects/views/secrets/types.ts
index b3ddd7150e..d428676a8b 100644
--- a/apps/design-system/src/subjects/views/secrets/types.ts
+++ b/apps/design-system/src/subjects/views/secrets/types.ts
@@ -4,4 +4,10 @@ export enum ScopeEnum {
   PROJECT = 'project'
 }
 
-export type SecretScope = 'account' | 'organization' | 'project'
+export type Scope = 'account' | 'organization' | 'project'
+
+export const scopeHierarchy: Record<Scope, { parent: Scope | null; child: Scope | null }> = {
+  account: { parent: null, child: ScopeEnum.ORGANIZATION },
+  organization: { parent: ScopeEnum.ACCOUNT, child: ScopeEnum.PROJECT },
+  project: { parent: ScopeEnum.ORGANIZATION, child: null }
+}
diff --git a/packages/ui/src/views/connectors/connector-header.tsx b/packages/ui/src/views/connectors/connector-header.tsx
new file mode 100644
index 0000000000..e92f4cac8d
--- /dev/null
+++ b/packages/ui/src/views/connectors/connector-header.tsx
@@ -0,0 +1,45 @@
+import { useForm } from 'react-hook-form'
+
+import { RadialOption, RadioSelect } from '@views/components/RadioSelect'
+
+import { ConnectorType } from './types'
+
+interface ConnectorTypeForm {
+  type: ConnectorType
+}
+
+export const ConnectorHeader = ({
+  onChange,
+  selectedType: selectedTypeVal
+}: {
+  onChange: (type: ConnectorType) => void
+  selectedType: ConnectorType
+}) => {
+  const { setValue } = useForm<ConnectorTypeForm>({
+    defaultValues: {
+      type: selectedTypeVal
+    }
+  })
+
+  const handleTypeChange = (value: ConnectorType) => {
+    setValue('type', value)
+    onChange(value)
+  }
+
+  const options: Array<RadialOption<ConnectorType>> = [
+    {
+      id: 'new-connector',
+      title: 'New Connector',
+      description: 'Create a new connector.',
+      value: ConnectorType.NEW
+    },
+    {
+      id: 'existing-connector',
+      title: 'Existing Connector',
+      description: 'Use an existing connector.',
+      value: ConnectorType.EXISTING
+    }
+  ]
+
+  return <RadioSelect options={options} value={selectedTypeVal} onValueChange={handleTypeChange} id="secret-type" />
+}
diff --git a/packages/ui/src/views/connectors/connector-reference/connector-reference.tsx b/packages/ui/src/views/connectors/connector-reference/connector-reference.tsx
new file mode 100644
index 0000000000..4d98da5ac9
--- /dev/null
+++ b/packages/ui/src/views/connectors/connector-reference/connector-reference.tsx
@@ -0,0 +1,100 @@
+import { Alert, Button, ButtonGroup, Icon, StackedList } from '@/components'
+import { EntityReference, EntityRendererProps } from '@views/platform'
+import { DirectionEnum } from '@views/platform/types'
+
+import { ConnectorItem } from '../types'
+
+export interface ConnectorReferenceProps {
+  // Data
+  connectorsData: ConnectorItem[]
+  childFolder: string | null
+  parentFolder: string | null
+  apiError?: string | null
+
+  // State
+  selectedEntity: ConnectorItem | null
+
+  // Callbacks
+  onSelectEntity: (entity: ConnectorItem) => void
+  onScopeChange: (direction: DirectionEnum) => void
+  onCancel?: () => void
+  isLoading?: boolean
+}
+
+// Component for selecting existing connectors
+export const ConnectorReference: React.FC<ConnectorReferenceProps> = ({
+  // Data
+  connectorsData,
+  childFolder,
+  parentFolder,
+  apiError,
+
+  // State
+  selectedEntity,
+
+  // Callbacks
+  onSelectEntity,
+  onScopeChange,
+  onCancel,
+  isLoading
+}) => {
+  // Define available scopes
+
+  // Custom entity renderer for connectors
+  const renderEntity = (props: EntityRendererProps<any>) => {
+    const { entity, isSelected, onSelect } = props
+
+    return (
+      <StackedList.Item
+        onClick={() => onSelect(entity)}
+        className={isSelected ? 'bg-background-4' : ''}
+        thumbnail={<Icon name="connectors" size={16} className="text-foreground-5" />}
+        actions={
+          <Button
+            variant="default"
+            size="sm"
+            onClick={() => {
+              onSelect(entity)
+            }}
+          >
+            Select
+          </Button>
+        }
+      >
+        <StackedList.Field title={entity.connector.name} description={entity.connector.description} />
+      </StackedList.Item>
+    )
+  }
+
+  return (
+    <div className="flex flex-col">
+      <span className="font-medium mb-4">Select an existing Connector:</span>
+      <div className="flex-1">
+        <EntityReference<ConnectorItem>
+          entities={connectorsData}
+          selectedEntity={selectedEntity}
+          onSelectEntity={onSelectEntity}
+          onScopeChange={onScopeChange}
+          renderEntity={renderEntity}
+          parentFolder={parentFolder}
+          childFolder={childFolder}
+          isLoading={isLoading}
+        />
+        {apiError ? (
+          <Alert.Container variant="destructive" className="mt-4">
+            <Alert.Description>{apiError}</Alert.Description>
+          </Alert.Container>
+        ) : null}
+      </div>
+
+      <div className="bg-background-2 fixed bottom-0 left-0 right-0 p-4 shadow-md">
+        <ButtonGroup className="flex flex-row justify-between">
+          <Button type="button" variant="outline" onClick={onCancel}>
+            Cancel
+          </Button>
+          <Button type="submit">Save</Button>
+        </ButtonGroup>
+      </div>
+    </div>
+  )
+}
diff --git a/packages/ui/src/views/connectors/index.ts b/packages/ui/src/views/connectors/index.ts
new file mode 100644
index 0000000000..2f3de5a802
--- /dev/null
+++ b/packages/ui/src/views/connectors/index.ts
@@ -0,0 +1,3 @@
+export * from './connector-reference/connector-reference'
+export * from './types'
+export * from './connector-header'
diff --git a/packages/ui/src/views/connectors/types.ts b/packages/ui/src/views/connectors/types.ts
new file mode 100644
index 0000000000..6f06fcdda3
--- /dev/null
+++ b/packages/ui/src/views/connectors/types.ts
@@ -0,0 +1,23 @@
+import { BaseEntityProps } from '@views/platform/types'
+
+export interface ConnectorFields {
+  name: string
+  identifier: string
+  description: string
+  accountIdentifier: string
+  orgIdentifier: string
+  projectIdentifier: string
+  tags: Record<string, string>
+  type: string
+}
+
+export interface ConnectorItem extends BaseEntityProps {
+  connector: ConnectorFields
+  createdAt: number
+  lastModifiedAt: number
+}
+
+export enum ConnectorType {
+  NEW = 'new',
+  EXISTING = 'existing'
+}
diff --git a/packages/ui/src/views/index.ts b/packages/ui/src/views/index.ts
index 1ed2f51174..f4758e1e30 100644
--- a/packages/ui/src/views/index.ts
+++ b/packages/ui/src/views/index.ts
@@ -60,3 +60,6 @@ export * from './components/RadioSelect'
 
 // entity-reference
 export * from './platform'
+
+// connectors
+export * from './connectors'


Reviews:
user: login: abhinavrastogi-harness
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-13T19:13:20Z
user: login: cjlee01
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T20:13:44Z
user: login: cjlee01
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T20:14:17Z
user: login: cjlee01
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T20:34:52Z

Review comments:
diff_hunk: @@ -0,0 +1,23 @@
+import { BaseEntityProps } from '@views/platform/types'
+
+export interface ConnectorFields {
+  name: string
+  identifier: string
+  description: string
+  accountIdentifier: string
+  orgIdentifier: string
+  projectIdentifier: string
+  tags: Record<string, string>
+  type: string
+}
+
+export interface ConnectorItem extends BaseEntityProps {
+  connector: ConnectorFields
+  createdAt: number
+  lastModifiedAt: number
+}
+
+export enum ConnectorType {
path: packages/ui/src/views/connectors/types.ts
user: login: abhinavrastogi-harness
body: can this be abstracted out as well? seems like a repeated piece.
created_at: 2025-03-13T19:13:05Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,105 @@
+import { useState } from 'react'
+
+import { Button, Drawer, Spacer } from '@harnessio/ui/components'
+import { ConnectorHeader, ConnectorItem, ConnectorReference, ConnectorType, DirectionEnum } from '@harnessio/ui/views'
+
+import mockAccountsData from '../secrets/mock-account-data.json'
+import mockOrgData from '../secrets/mock-org-data.json'
+import mockProjectsData from '../secrets/mock-project-data.json'
+import { Scope, ScopeEnum, scopeHierarchy } from '../secrets/types'
+import mockConnectorsData from './mock-connectors-data.json'
+
+export const ConnectorsPage = () => {
+  const [selectedType, setSelectedType] = useState<ConnectorType>(ConnectorType.EXISTING)
+  const [, setActiveScope] = useState<Scope>(ScopeEnum.ORGANIZATION)
+
+  // State for existing connectors
+  const [selectedConnector, setSelectedConnector] = useState<ConnectorItem | null>(null)
+  const [parentFolder, setParentFolder] = useState<string | null>(mockAccountsData[0].accountName)
+  const [childFolder, setChildFolder] = useState<string | null>(mockProjectsData[0].projectResponse.project.identifier)
+
+  // Handlers for existing connectors
+  const handleSelectConnector = (connector: ConnectorItem) => {
+    setSelectedConnector(connector)
+    console.log('Selected connector:', connector)
path: apps/design-system/src/subjects/views/connectors/connectors.tsx
user: login: cjlee01
body: remove console log
created_at: 2025-03-13T20:13:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,105 @@
+import { useState } from 'react'
+
+import { Button, Drawer, Spacer } from '@harnessio/ui/components'
+import { ConnectorHeader, ConnectorItem, ConnectorReference, ConnectorType, DirectionEnum } from '@harnessio/ui/views'
+
+import mockAccountsData from '../secrets/mock-account-data.json'
+import mockOrgData from '../secrets/mock-org-data.json'
+import mockProjectsData from '../secrets/mock-project-data.json'
+import { Scope, ScopeEnum, scopeHierarchy } from '../secrets/types'
+import mockConnectorsData from './mock-connectors-data.json'
+
+export const ConnectorsPage = () => {
+  const [selectedType, setSelectedType] = useState<ConnectorType>(ConnectorType.EXISTING)
+  const [, setActiveScope] = useState<Scope>(ScopeEnum.ORGANIZATION)
+
+  // State for existing connectors
+  const [selectedConnector, setSelectedConnector] = useState<ConnectorItem | null>(null)
+  const [parentFolder, setParentFolder] = useState<string | null>(mockAccountsData[0].accountName)
+  const [childFolder, setChildFolder] = useState<string | null>(mockProjectsData[0].projectResponse.project.identifier)
+
+  // Handlers for existing connectors
+  const handleSelectConnector = (connector: ConnectorItem) => {
+    setSelectedConnector(connector)
+    console.log('Selected connector:', connector)
+  }
+
+  const handleScopeChange = (direction: DirectionEnum) => {
+    setActiveScope(prevScope => {
+      const newScope =
+        direction === DirectionEnum.PARENT ? scopeHierarchy[prevScope].parent! : scopeHierarchy[prevScope].child!
+      switch (newScope) {
+        case ScopeEnum.ACCOUNT:
+          setParentFolder(null)
+          setChildFolder(mockOrgData[0].organizationResponse.organization.identifier)
+          break
+        case ScopeEnum.ORGANIZATION:
+          setParentFolder(mockAccountsData[0].accountName)
+          setChildFolder(mockProjectsData[0].projectResponse.project.identifier)
+          break
+        case ScopeEnum.PROJECT:
+          setParentFolder(mockOrgData[0].organizationResponse.organization.identifier)
+          setChildFolder(null)
+          break
+      }
+      return newScope
+    })
+  }
+
+  const handleCancel = () => {
+    console.log('Cancelled')
path: apps/design-system/src/subjects/views/connectors/connectors.tsx
user: login: cjlee01
body: should cancel close drawer?
created_at: 2025-03-13T20:14:17Z
author_association: CONTRIBUTOR

PR comments:
user: login: netlify[bot]
created_at: 2025-03-13T18:55:49Z
body: ### <span aria-hidden="true">✅</span> Deploy Preview for *harness-design* ready!


|  Name | Link |
|:-:|------------------------|
|<span aria-hidden="true">🔨</span> Latest commit | 1f4730c6b291472e4bfe36b93145cf69c3ee2d36 |
|<span aria-hidden="true">🔍</span> Latest deploy log | https://app.netlify.com/sites/harness-design/deploys/67d32a290d36a200070fac9b |
|<span aria-hidden="true">😎</span> Deploy Preview | [https://deploy-preview-1250--harness-design.netlify.app](https://deploy-preview-1250--harness-design.netlify.app) |
|<span aria-hidden="true">📱</span> Preview on mobile | <details><summary> Toggle QR Code... </summary><br /><br />![QR Code](https://app.netlify.com/qr-code/eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1cmwiOiJodHRwczovL2RlcGxveS1wcmV2aWV3LTEyNTAtLWhhcm5lc3MtZGVzaWduLm5ldGxpZnkuYXBwIn0.2akHBhi0Lr4rjsgwSsd7V9FGqlCGgevzVvBcbbxK3No)<br /><br />_Use your smartphone camera to open QR code link._</details> |
---
<!-- [harness-design Preview](https://deploy-preview-1250--harness-design.netlify.app) -->
_To edit notification comments on pull requests, go to your [Netlify site configuration](https://app.netlify.com/sites/harness-design/configuration/notifications#deploy-webhooks)._
author_association: NONE
user: login: netlify[bot]
created_at: 2025-03-13T18:55:52Z
body: ### <span aria-hidden="true">✅</span> Deploy Preview for *harness-xd-review* ready!


|  Name | Link |
|:-:|------------------------|
|<span aria-hidden="true">🔨</span> Latest commit | 1f4730c6b291472e4bfe36b93145cf69c3ee2d36 |
|<span aria-hidden="true">🔍</span> Latest deploy log | https://app.netlify.com/sites/harness-xd-review/deploys/67d32a29d2d3f70008437b1d |
|<span aria-hidden="true">😎</span> Deploy Preview | [https://deploy-preview-1250--harness-xd-review.netlify.app](https://deploy-preview-1250--harness-xd-review.netlify.app) |
|<span aria-hidden="true">📱</span> Preview on mobile | <details><summary> Toggle QR Code... </summary><br /><br />![QR Code](https://app.netlify.com/qr-code/eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1cmwiOiJodHRwczovL2RlcGxveS1wcmV2aWV3LTEyNTAtLWhhcm5lc3MteGQtcmV2aWV3Lm5ldGxpZnkuYXBwIn0.kuaGgqhuImQBNGqyxxsJJqobKNBH8-mYCTA_2soPN4I)<br /><br />_Use your smartphone camera to open QR code link._</details> |
---
<!-- [harness-xd-review Preview](https://deploy-preview-1250--harness-xd-review.netlify.app) -->
_To edit notification comments on pull requests, go to your [Netlify site configuration](https://app.netlify.com/sites/harness-xd-review/configuration/notifications#deploy-webhooks)._
author_association: NONE