diff --git a/src/actions/organizationSettings.ts b/src/actions/organizationSettings.ts index 880e656f4b..3f1ff19d15 100644 --- a/src/actions/organizationSettings.ts +++ b/src/actions/organizationSettings.ts @@ -11,6 +11,7 @@ import { type OrganizationSettings, organizationAllowListSchema, organizationDefaultSettingsSchema, + organizationCloudSettingsSchema, } from '@/types'; import { AuditLogTargetType, client as db, orgSettings } from '@/db/server'; import { handleError, isAuthSuccess } from '@/lib/server'; @@ -18,9 +19,7 @@ import { handleError, isAuthSuccess } from '@/lib/server'; import { validateAuth } from './auth'; import { insertAuditLog } from './auditLogs'; -export async function getOrganizationSettings(): Promise< - OrganizationSettings | undefined -> { +export async function getOrganizationSettings(): Promise { const { userId, orgId } = await auth(); if (!userId) { @@ -37,7 +36,7 @@ export async function getOrganizationSettings(): Promise< .where(eq(orgSettings.orgId, orgId)) .limit(1); - return settings.length === 0 ? ORGANIZATION_DEFAULT : settings[0]; + return settings[0] || ORGANIZATION_DEFAULT; } /** @@ -47,12 +46,16 @@ const updateOrganizationSchema = z .object({ defaultSettings: organizationDefaultSettingsSchema.optional(), allowList: organizationAllowListSchema.optional(), + cloudSettings: organizationCloudSettingsSchema.optional(), }) .refine( (data) => - data.defaultSettings !== undefined || data.allowList !== undefined, + data.defaultSettings !== undefined || + data.allowList !== undefined || + data.cloudSettings !== undefined, { - message: 'At least one of defaultSettings or allowList must be provided', + message: + 'At least one of defaultSettings, allowList, or cloudSettings must be provided', }, ); @@ -96,12 +99,17 @@ export async function updateOrganization( updateData.allowList = validatedData.allowList; } + if (validatedData.cloudSettings) { + updateData.cloudSettings = validatedData.cloudSettings; + } + if (isNewRecord) { await tx.insert(orgSettings).values({ orgId, version: 1, defaultSettings: validatedData.defaultSettings || {}, allowList: validatedData.allowList || ORGANIZATION_ALLOW_ALL, + cloudSettings: validatedData.cloudSettings || {}, }); } else { await tx @@ -135,6 +143,17 @@ export async function updateOrganization( description: 'Updated organization allow list', }); } + + if (validatedData.cloudSettings) { + await insertAuditLog(tx, { + userId, + orgId, + targetType: AuditLogTargetType.CLOUD_SETTINGS, + targetId: 'organization-cloud-settings', + newValue: validatedData.cloudSettings, + description: 'Updated organization cloud settings', + }); + } }); return { diff --git a/src/app/(authenticated)/org/[[...organization-profile]]/CloudSettings.tsx b/src/app/(authenticated)/org/[[...organization-profile]]/CloudSettings.tsx new file mode 100644 index 0000000000..a3738d0a21 --- /dev/null +++ b/src/app/(authenticated)/org/[[...organization-profile]]/CloudSettings.tsx @@ -0,0 +1,162 @@ +'use client'; + +import { useTranslations } from 'next-intl'; +import { useState } from 'react'; +import { useForm } from 'react-hook-form'; +import { useQuery, useQueryClient } from '@tanstack/react-query'; +import { toast } from 'sonner'; +import { Cloud } from 'lucide-react'; + +import { + type OrganizationSettings, + type OrganizationCloudSettings, +} from '@/types'; +import { + getOrganizationSettings, + updateOrganization, +} from '@/actions/organizationSettings'; +import { + Button, + Checkbox, + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, +} from '@/components/ui'; + +type CloudSettingsFormValues = { + recordTaskMessages: boolean; +}; + +type CloudSettingsFormProps = { + orgSettings: OrganizationSettings; + queryClient: ReturnType; +}; + +const CloudSettingsForm = ({ + orgSettings, + queryClient, +}: CloudSettingsFormProps) => { + const t = useTranslations('CloudSettings'); + const [isSaving, setIsSaving] = useState(false); + + const form = useForm({ + defaultValues: { + recordTaskMessages: + orgSettings.cloudSettings?.recordTaskMessages ?? false, + }, + }); + + const onSubmit = async (data: CloudSettingsFormValues) => { + setIsSaving(true); + + try { + const cloudSettings: OrganizationCloudSettings = { + recordTaskMessages: data.recordTaskMessages, + }; + + const result = await updateOrganization({ cloudSettings }); + + if (result.success) { + queryClient.invalidateQueries({ + queryKey: ['getOrganizationSettings'], + }); + toast.success('Cloud settings saved successfully'); + } else { + throw new Error(result.error || 'An unexpected error occurred.'); + } + } catch (error) { + console.error('Failed to update cloud settings:', error); + toast.error('Failed to save cloud settings. Please try again.'); + } finally { + setIsSaving(false); + } + }; + + return ( + <> +
+
+

+ {t('cloud_section_title')} +

+
+
+

+ {t('cloud_section_description')} +

+
+
+ +
+
+ +

Task Recording

+
+
+ ( + + + + +
+ {t('record_task_messages')} + + {t('record_task_messages_description')} + +
+
+ )} + /> +
+
+ +
+ +
+
+ +
+ + ); +}; + +export const CloudSettings = () => { + const queryClient = useQueryClient(); + + const { data: orgSettings } = useQuery({ + queryKey: ['getOrganizationSettings'], + queryFn: getOrganizationSettings, + }); + + if (!orgSettings) { + return ( +
+
+ Loading settings... +
+ ); + } + + return ( + + ); +}; diff --git a/src/app/(authenticated)/org/[[...organization-profile]]/Org.tsx b/src/app/(authenticated)/org/[[...organization-profile]]/Org.tsx index a1d93e38dd..474077d6bd 100644 --- a/src/app/(authenticated)/org/[[...organization-profile]]/Org.tsx +++ b/src/app/(authenticated)/org/[[...organization-profile]]/Org.tsx @@ -2,10 +2,11 @@ import { useTranslations } from 'next-intl'; import { OrganizationProfile } from '@clerk/nextjs'; -import { ListTodo, SlidersHorizontal } from 'lucide-react'; +import { ListTodo, SlidersHorizontal, Cloud } from 'lucide-react'; import { DefaultParameters } from './DefaultParameters'; import { ProviderWhitelist } from './ProviderWhitelist'; +import { CloudSettings } from './CloudSettings'; export const Org = () => { const t = useTranslations('OrganizationProfile'); @@ -24,6 +25,13 @@ export const Org = () => { > + } + > + + orgs.id), // Assigned by Clerk. version: integer('version').notNull().default(1), + cloudSettings: jsonb('cloud_settings') + .notNull() + .$type() + .default({}), defaultSettings: jsonb('default_settings') .notNull() .$type() diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 1468e77a08..0851446c40 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -17,6 +17,7 @@ }, "OrganizationProfile": { "provider_whitelist": "Provider Whitelist", + "cloud_settings": "Cloud Settings", "default_parameters": "Default Parameters" }, "ProviderWhitelist": { @@ -27,6 +28,12 @@ "parameters_section_title": "Default Parameters", "parameters_section_description": "Set global default parameters for AI model calls" }, + "CloudSettings": { + "cloud_section_title": "Cloud Settings", + "cloud_section_description": "Configure how your organization's data is handled in the cloud.", + "record_task_messages": "Record task messages", + "record_task_messages_description": "When enabled task messages and interactions will be recorded." + }, "Analytics": { "view_mode_title": "View By", "view_mode_developers": "Developers", diff --git a/src/i18n/locales/fr.json b/src/i18n/locales/fr.json index d1964d8e4b..03894fa6db 100644 --- a/src/i18n/locales/fr.json +++ b/src/i18n/locales/fr.json @@ -17,6 +17,7 @@ }, "OrganizationProfile": { "provider_whitelist": "Liste blanche des fournisseurs", + "cloud_settings": "Paramètres Cloud", "default_parameters": "Paramètres par défaut" }, "ProviderWhitelist": { @@ -27,6 +28,12 @@ "parameters_section_title": "Paramètres par défaut", "parameters_section_description": "Définissez les paramètres par défaut globaux pour les appels aux modèles d'IA" }, + "CloudSettings": { + "cloud_section_title": "Paramètres Cloud", + "cloud_section_description": "Configurez la manière dont les données de votre organisation sont gérées dans le cloud.", + "record_task_messages": "Enregistrer les messages de tâche", + "record_task_messages_description": "Lorsque cette option est activée, les messages et interactions de tâche seront enregistrés." + }, "Analytics": { "view_mode_title": "Afficher Par", "view_mode_developers": "Développeurs", diff --git a/src/types/org.ts b/src/types/org.ts index 0c8f95ce81..11f3b2e53f 100644 --- a/src/types/org.ts +++ b/src/types/org.ts @@ -40,8 +40,17 @@ export type OrganizationDefaultSettings = z.infer< typeof organizationDefaultSettingsSchema >; +export const organizationCloudSettingsSchema = z.object({ + recordTaskMessages: z.boolean().optional(), +}); + +export type OrganizationCloudSettings = z.infer< + typeof organizationCloudSettingsSchema +>; + export const organizationSettingsSchema = z.object({ version: z.number(), + cloudSettings: organizationCloudSettingsSchema.optional(), defaultSettings: organizationDefaultSettingsSchema, allowList: organizationAllowListSchema, }); @@ -50,6 +59,7 @@ export type OrganizationSettings = z.infer; export const ORGANIZATION_DEFAULT: OrganizationSettings = { version: 0, + cloudSettings: {}, defaultSettings: {}, allowList: ORGANIZATION_ALLOW_ALL, } as const;