Add settins connector view

Signed-off-by: Bryan Frimin <bryan@getprobo.com>
This commit is contained in:
Bryan Frimin
2025-04-20 20:00:48 -07:00
parent 85935ac1dc
commit 9aeed6da4a
14 changed files with 1823 additions and 132 deletions

View File

@@ -23,6 +23,7 @@ import {
useMutation,
} from "react-relay";
import { useParams } from "react-router";
import { Link } from "react-router";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { useToast } from "@/hooks/use-toast";
@@ -41,6 +42,21 @@ import type { SettingsViewRemoveUserMutation as SettingsViewRemoveUserMutationTy
import { PageTemplate } from "@/components/PageTemplate";
import { SettingsViewSkeleton } from "./SettingsPage";
// Define the connector type until the generated type is available
interface Connector {
id: string;
name: string;
type: string;
createdAt: string;
}
interface AvailableConnector {
id: string;
name: string;
type: string;
description: string;
}
const settingsViewQuery = graphql`
query SettingsViewQuery($organizationID: ID!) {
organization: node(id: $organizationID) {
@@ -58,6 +74,16 @@ const settingsViewQuery = graphql`
}
}
}
connectors(first: 100) {
edges {
node {
id
name
type
createdAt
}
}
}
}
}
}
@@ -101,6 +127,7 @@ function SettingsViewContent({
const data = usePreloadedQuery(settingsViewQuery, queryRef);
const organization = data.organization;
const users = organization.users?.edges.map((edge) => edge.node) || [];
const connectors = (organization as any).connectors?.edges.map((edge: any) => edge.node) || [];
const { toast } = useToast();
const fileInputRef = useRef<HTMLInputElement>(null);
@@ -115,6 +142,18 @@ function SettingsViewContent({
const [isUploading, setIsUploading] = useState(false);
const [isRemoving, setIsRemoving] = useState(false);
// Available connectors
const availableConnectors: AvailableConnector[] = [
{ id: "github", name: "GitHub", type: "oauth2", description: "Connect to GitHub repositories and issues" },
{ id: "slack", name: "Slack", type: "oauth2", description: "Connect to Slack workspace and channels" },
];
// Filter out connectors that are already connected
const connectedConnectorIds = connectors.map((connector: Connector) => connector.name);
const notConnectedConnectors = availableConnectors.filter(
connector => !connectedConnectorIds.includes(connector.id)
);
const [updateOrganization] =
useMutation<SettingsViewUpdateOrganizationMutationType>(
updateOrganizationMutation
@@ -129,7 +168,7 @@ function SettingsViewContent({
const { organizationId } = useParams();
const [, loadQuery] =
useQueryLoader<SettingsViewQueryType>(settingsViewQuery);
const handleUpdateName = () => {
updateOrganization({
variables: {
@@ -444,6 +483,96 @@ function SettingsViewContent({
</div>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between">
<div>
<CardTitle>Integrations</CardTitle>
<CardDescription>
Connect to third-party services to enhance your workflow
</CardDescription>
</div>
</CardHeader>
<CardContent>
<div className="space-y-6">
{connectors.length > 0 && (
<div>
<h3 className="mb-4 text-sm font-medium">Connected services</h3>
<div className="space-y-4">
{connectors.map((connector: Connector) => (
<div
key={connector.id}
className="flex items-center justify-between rounded-lg border p-3 shadow-xs"
>
<div className="flex items-center gap-3">
<div className="flex h-10 w-10 items-center justify-center rounded-lg border bg-subtle-bg">
<Building2 className="h-5 w-5 text-tertiary" />
</div>
<div className="flex flex-col">
<span className="text-sm font-medium">
{connector.name}
</span>
<span className="text-sm text-tertiary">
{connector.type} · Connected on {new Date(connector.createdAt).toLocaleDateString()}
</span>
</div>
</div>
</div>
))}
</div>
</div>
)}
{notConnectedConnectors.length > 0 && (
<div>
<h3 className="mb-4 text-sm font-medium">Available services</h3>
<div className="space-y-4">
{notConnectedConnectors.map((connector) => (
<div
key={connector.id}
className="flex items-center justify-between rounded-lg border p-3 shadow-xs"
>
<div className="flex items-center gap-3">
<div className="flex h-10 w-10 items-center justify-center rounded-lg border bg-subtle-bg">
<Building2 className="h-5 w-5 text-tertiary" />
</div>
<div className="flex flex-col">
<span className="text-sm font-medium">
{connector.name}
</span>
<span className="text-sm text-tertiary">
{connector.description}
</span>
</div>
</div>
<div className="flex items-center gap-2">
<a
href={`${process.env.API_SERVER_HOST}/api/console/v1/connectors/initiate?organization_id=${encodeURIComponent(organization.id)}&connector_id=${encodeURIComponent(connector.id)}&continue=${encodeURIComponent(window.location.href)}`}
className="inline-flex items-center justify-center h-9 px-3 text-sm font-medium rounded-md border border-input bg-background hover:bg-accent hover:text-accent-foreground"
>
Connect
</a>
</div>
</div>
))}
</div>
</div>
)}
{connectors.length === 0 && notConnectedConnectors.length === 0 && (
<div className="flex flex-col items-center justify-center p-8 text-center">
<div className="rounded-full bg-subtle-bg p-3">
<Building2 className="h-6 w-6 text-tertiary" />
</div>
<h3 className="mt-4 text-lg font-medium">No integrations available</h3>
<p className="mt-2 text-sm text-tertiary">
There are currently no integrations available for your workspace.
</p>
</div>
)}
</div>
</CardContent>
</Card>
</div>
<Dialog open={isEditNameOpen} onOpenChange={setIsEditNameOpen}>

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<d22ffd34989ddeb523e19aab24261e58>>
* @generated SignedSource<<302c6e908c1aea64db4820b83e5e2bcd>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -14,6 +14,16 @@ export type SettingsViewQuery$variables = {
};
export type SettingsViewQuery$data = {
readonly organization: {
readonly connectors?: {
readonly edges: ReadonlyArray<{
readonly node: {
readonly createdAt: string;
readonly id: string;
readonly name: string;
readonly type: string;
};
}>;
};
readonly id: string;
readonly logoUrl?: string | null | undefined;
readonly name?: string;
@@ -57,15 +67,30 @@ v2 = {
"storageKey": null
},
v3 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
v4 = [
{
"kind": "Literal",
"name": "first",
"value": 100
}
],
v5 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "createdAt",
"storageKey": null
},
v6 = {
"kind": "InlineFragment",
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
(v3/*: any*/),
{
"alias": null,
"args": null,
@@ -75,13 +100,7 @@ v3 = {
},
{
"alias": null,
"args": [
{
"kind": "Literal",
"name": "first",
"value": 100
}
],
"args": (v4/*: any*/),
"concreteType": "UserConnection",
"kind": "LinkedField",
"name": "users",
@@ -118,13 +137,7 @@ v3 = {
"name": "email",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "createdAt",
"storageKey": null
}
(v5/*: any*/)
],
"storageKey": null
}
@@ -133,6 +146,49 @@ v3 = {
}
],
"storageKey": "users(first:100)"
},
{
"alias": null,
"args": (v4/*: any*/),
"concreteType": "ConnectorConnection",
"kind": "LinkedField",
"name": "connectors",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "ConnectorEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Connector",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v2/*: any*/),
(v3/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "type",
"storageKey": null
},
(v5/*: any*/)
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "connectors(first:100)"
}
],
"type": "Organization",
@@ -154,7 +210,7 @@ return {
"plural": false,
"selections": [
(v2/*: any*/),
(v3/*: any*/)
(v6/*: any*/)
],
"storageKey": null
}
@@ -184,23 +240,23 @@ return {
"storageKey": null
},
(v2/*: any*/),
(v3/*: any*/)
(v6/*: any*/)
],
"storageKey": null
}
]
},
"params": {
"cacheID": "1749c3e17b6efd13be678a0e968b7ac4",
"cacheID": "e243498081f3875e74172abba80a88e8",
"id": null,
"metadata": {},
"name": "SettingsViewQuery",
"operationKind": "query",
"text": "query SettingsViewQuery(\n $organizationID: ID!\n) {\n organization: node(id: $organizationID) {\n __typename\n id\n ... on Organization {\n name\n logoUrl\n users(first: 100) {\n edges {\n node {\n id\n fullName\n email\n createdAt\n }\n }\n }\n }\n }\n}\n"
"text": "query SettingsViewQuery(\n $organizationID: ID!\n) {\n organization: node(id: $organizationID) {\n __typename\n id\n ... on Organization {\n name\n logoUrl\n users(first: 100) {\n edges {\n node {\n id\n fullName\n email\n createdAt\n }\n }\n }\n connectors(first: 100) {\n edges {\n node {\n id\n name\n type\n createdAt\n }\n }\n }\n }\n }\n}\n"
}
};
})();
(node as any).hash = "147757d21500b3eb42848ebe8a43bd9f";
(node as any).hash = "52b24e3845bb31016ddad6f815be1cdb";
export default node;