Skip to main content
Glama
index.tsx8.21 kB
import { t } from 'i18next'; import { Bell, GitBranch, Puzzle, Settings, Users } from 'lucide-react'; import { useState, useEffect, useRef } from 'react'; import { useForm } from 'react-hook-form'; import { McpSvg } from '@/assets/img/custom/mcp'; import { Button } from '@/components/ui/button'; import { Dialog, DialogContent } from '@/components/ui/dialog'; import { ScrollArea } from '@/components/ui/scroll-area'; import { LoadingSpinner } from '@/components/ui/spinner'; import { useAuthorization } from '@/hooks/authorization-hooks'; import { flagsHooks } from '@/hooks/flags-hooks'; import { platformHooks } from '@/hooks/platform-hooks'; import { projectHooks } from '@/hooks/project-hooks'; import { userHooks } from '@/hooks/user-hooks'; import { cn } from '@/lib/utils'; import { ApFlagId, isNil, Permission, PlatformRole, ProjectType, } from '@activepieces/shared'; import { ApProjectDisplay } from '../ap-project-display'; import { ProjectAvatar } from '../project-avatar'; import { AlertsSettings } from './alerts'; import { EnvironmentSettings } from './environment'; import { GeneralSettings, FormValues } from './general'; import { useGeneralSettingsMutation } from './general/hook'; import { McpServerSettings } from './mcp-server'; import { MembersSettings } from './members'; import { PiecesSettings } from './pieces'; type TabId = | 'general' | 'members' | 'alerts' | 'pieces' | 'environment' | 'mcp'; interface ProjectSettingsDialogProps { open: boolean; onClose: () => void; initialTab?: TabId; initialValues?: { projectName?: string; externalId?: string; }; } export function ProjectSettingsDialog({ open, onClose, initialTab = 'general', initialValues, }: ProjectSettingsDialogProps) { const [activeTab, setActiveTab] = useState<TabId>(initialTab); const { checkAccess } = useAuthorization(); const { project } = projectHooks.useCurrentProject(); const previousOpenRef = useRef(open); const { data: showAlerts } = flagsHooks.useFlag(ApFlagId.SHOW_ALERTS); const { data: showProjectMembers } = flagsHooks.useFlag( ApFlagId.SHOW_PROJECT_MEMBERS, ); const { platform } = platformHooks.useCurrentPlatform(); const platformRole = userHooks.getCurrentUserPlatformRole(); const form = useForm<FormValues>({ defaultValues: { projectName: initialValues?.projectName, icon: project.icon, externalId: initialValues?.externalId, }, disabled: checkAccess(Permission.WRITE_PROJECT) === false, }); const projectMutation = useGeneralSettingsMutation(project.id, form); const handleSave = (values: FormValues) => { projectMutation.mutate({ displayName: values.projectName, icon: values.icon, externalId: values.externalId, }); }; useEffect(() => { const dialogJustOpened = open && !previousOpenRef.current; if (dialogJustOpened && !isNil(project)) { form.reset({ ...initialValues, icon: project.icon, }); setActiveTab(initialTab); } previousOpenRef.current = open; }, [open, project]); const hasGeneralSettings = project.type === ProjectType.TEAM || (platform.plan.embeddingEnabled && platformRole === PlatformRole.ADMIN); const tabs = [ { id: 'general' as TabId, label: t('General'), icon: <Settings className="w-4 h-4" />, disabled: !hasGeneralSettings, }, { id: 'members' as TabId, label: t('Members'), icon: <Users className="w-4 h-4" />, disabled: project.type !== ProjectType.TEAM || !checkAccess(Permission.READ_PROJECT_MEMBER) || !showProjectMembers, }, { id: 'alerts' as TabId, label: t('Alerts'), icon: <Bell className="w-4 h-4" />, disabled: project.type !== ProjectType.TEAM || !checkAccess(Permission.READ_ALERT) || !showAlerts, }, { id: 'pieces' as TabId, label: t('Pieces'), icon: <Puzzle className="w-4 h-4" />, disabled: false, }, { id: 'environment' as TabId, label: t('Environment'), icon: <GitBranch className="w-4 h-4" />, disabled: !checkAccess(Permission.READ_PROJECT_RELEASE), }, { id: 'mcp' as TabId, label: t('MCP Server'), icon: <McpSvg className="w-4 h-4" />, disabled: false, }, ].filter((tab) => !tab.disabled); const renderTabContent = () => { switch (activeTab) { case 'general': return ( <GeneralSettings form={form} isSaving={projectMutation.isPending} /> ); case 'members': return <MembersSettings />; case 'alerts': return <AlertsSettings />; case 'pieces': return <PiecesSettings />; case 'environment': return <EnvironmentSettings />; case 'mcp': return <McpServerSettings />; default: return null; } }; const renderTabHeader = () => { return ( <span className="text-lg font-bold"> {tabs.find((tab) => tab.id === activeTab)?.label} </span> ); }; const renderDialogFooter = () => { if (activeTab !== 'general') return null; return ( <div className="border-t bg-background rounded-br-md"> <div className="flex items-center justify-end gap-3 px-6 py-4"> <Button variant="outline" size="sm" onClick={onClose} disabled={projectMutation.isPending} > {t('Close')} </Button> <Button disabled={projectMutation.isPending} size="sm" onClick={form.handleSubmit(handleSave)} > {projectMutation.isPending ? ( <> <LoadingSpinner className="w-4 h-4 mr-2" /> {t('Saving...')} </> ) : ( t('Save Changes') )} </Button> </div> </div> ); }; return ( <Dialog open={open} onOpenChange={onClose}> <DialogContent className="max-w-5xl w-full max-h-[95vh] rounded-sm flex flex-col p-0"> <div className="flex h-[700px]"> <div className="w-[238px]"> <nav className="bg-sidebar space-y-1 bg-muted rounded-sm rounded-r-none h-full flex flex-col rounded-l-md"> <ApProjectDisplay title={project.displayName} icon={project.icon} containerClassName="px-3 my-4" titleClassName="text-md font-bold" maxLengthToNotShowTooltip={18} projectType={project.type} /> <div className="flex flex-col px-2 gap-1"> {tabs.map((tab) => ( <div key={tab.id} className={cn( 'flex items-center gap-2 rounded-sm px-2 py-1.5 text-sm font-medium transition-all cursor-pointer hover:bg-sidebar-active', { 'bg-sidebar-active': activeTab === tab.id, }, )} onClick={() => setActiveTab(tab.id)} > {tab.icon} {tab.label} </div> ))} </div> </nav> </div> <div className="flex-1 min-w-0 flex flex-col"> <div className="flex-1 min-h-0 overflow-hidden"> <ScrollArea className="h-full"> {activeTab === 'general' && ( <ProjectAvatar displayName={project.displayName} projectType={project.type} iconColor={project.icon.color} size="md" showBackground={true} /> )} <div className="flex flex-col gap-3 px-10 pt-4"> {renderTabHeader()} {renderTabContent()} </div> </ScrollArea> </div> {renderDialogFooter()} </div> </div> </DialogContent> </Dialog> ); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/activepieces/activepieces'

If you have feedback or need assistance with the MCP directory API, please join our Discord server