diff --git a/apps/app/src/app/(app)/[orgId]/integrations/components/PlatformIntegrations.test.tsx b/apps/app/src/app/(app)/[orgId]/integrations/components/PlatformIntegrations.test.tsx
index 31a2f3cee..b10e3ff0f 100644
--- a/apps/app/src/app/(app)/[orgId]/integrations/components/PlatformIntegrations.test.tsx
+++ b/apps/app/src/app/(app)/[orgId]/integrations/components/PlatformIntegrations.test.tsx
@@ -92,6 +92,7 @@ vi.mock('next/link', () => ({
// Mock next/navigation
vi.mock('next/navigation', () => ({
useParams: () => ({ orgId: 'org-1' }),
+ useRouter: () => ({ push: vi.fn() }),
useSearchParams: () => new URLSearchParams(),
}));
@@ -143,9 +144,10 @@ vi.mock('lucide-react', () => ({
// Mock sonner
vi.mock('sonner', () => ({
- toast: { success: vi.fn(), error: vi.fn() },
+ toast: { success: vi.fn(), error: vi.fn(), info: vi.fn() },
}));
+import { toast } from 'sonner';
import { PlatformIntegrations } from './PlatformIntegrations';
const defaultProps = {
@@ -202,4 +204,129 @@ describe('PlatformIntegrations', () => {
expect(screen.getByTestId('search-input')).toBeInTheDocument();
});
});
+
+ describe('Employee sync import prompt', () => {
+ it('shows import prompt toast after Google Workspace OAuth callback', async () => {
+ // Override mocks for this test to simulate OAuth callback
+ const { useIntegrationProviders, useIntegrationConnections } = vi.mocked(
+ await import('@/hooks/use-integration-platform'),
+ );
+
+ vi.mocked(useIntegrationProviders).mockReturnValue({
+ providers: [
+ {
+ id: 'google-workspace',
+ name: 'Google Workspace',
+ description: 'Google Workspace admin',
+ category: 'Identity & Access',
+ logoUrl: '/google.png',
+ authType: 'oauth2',
+ oauthConfigured: true,
+ isActive: true,
+ requiredVariables: [],
+ mappedTasks: [],
+ supportsMultipleConnections: false,
+ },
+ ] as any,
+ isLoading: false,
+ error: undefined,
+ refresh: vi.fn(),
+ });
+
+ vi.mocked(useIntegrationConnections).mockReturnValue({
+ connections: [
+ {
+ id: 'conn-1',
+ providerSlug: 'google-workspace',
+ status: 'active',
+ variables: null,
+ },
+ ] as any,
+ isLoading: false,
+ error: undefined,
+ refresh: vi.fn(),
+ });
+
+ // Mock useSearchParams to simulate OAuth callback
+ const { useSearchParams: mockUseSearchParams } = vi.mocked(
+ await import('next/navigation'),
+ );
+ vi.mocked(mockUseSearchParams).mockReturnValue(
+ new URLSearchParams('success=true&provider=google-workspace') as any,
+ );
+
+ setMockPermissions(ADMIN_PERMISSIONS);
+
+ render();
+
+ expect(toast.success).toHaveBeenCalledWith(
+ 'Google Workspace connected successfully!',
+ );
+ expect(toast.info).toHaveBeenCalledWith(
+ 'Import your Google Workspace users',
+ expect.objectContaining({
+ description: 'Go to People to import and sync your team members.',
+ action: expect.objectContaining({ label: 'Go to People' }),
+ }),
+ );
+ });
+
+ it('does not show import prompt for non-sync providers', async () => {
+ // Override mocks for this test to simulate OAuth callback for a non-sync provider
+ const { useIntegrationProviders, useIntegrationConnections } = vi.mocked(
+ await import('@/hooks/use-integration-platform'),
+ );
+
+ vi.mocked(useIntegrationProviders).mockReturnValue({
+ providers: [
+ {
+ id: 'github',
+ name: 'GitHub',
+ description: 'Code hosting',
+ category: 'Development',
+ logoUrl: '/github.png',
+ authType: 'oauth2',
+ oauthConfigured: true,
+ isActive: true,
+ requiredVariables: [],
+ mappedTasks: [],
+ supportsMultipleConnections: false,
+ },
+ ] as any,
+ isLoading: false,
+ error: undefined,
+ refresh: vi.fn(),
+ });
+
+ vi.mocked(useIntegrationConnections).mockReturnValue({
+ connections: [
+ {
+ id: 'conn-2',
+ providerSlug: 'github',
+ status: 'active',
+ variables: null,
+ },
+ ] as any,
+ isLoading: false,
+ error: undefined,
+ refresh: vi.fn(),
+ });
+
+ const { useSearchParams: mockUseSearchParams } = vi.mocked(
+ await import('next/navigation'),
+ );
+ vi.mocked(mockUseSearchParams).mockReturnValue(
+ new URLSearchParams('success=true&provider=github') as any,
+ );
+
+ setMockPermissions(ADMIN_PERMISSIONS);
+
+ render();
+
+ expect(toast.success).toHaveBeenCalledWith(
+ 'GitHub connected successfully!',
+ );
+ expect(toast.info).not.toHaveBeenCalled();
+ });
+ });
});
diff --git a/apps/app/src/app/(app)/[orgId]/integrations/components/PlatformIntegrations.tsx b/apps/app/src/app/(app)/[orgId]/integrations/components/PlatformIntegrations.tsx
index 594f8d342..700704c3e 100644
--- a/apps/app/src/app/(app)/[orgId]/integrations/components/PlatformIntegrations.tsx
+++ b/apps/app/src/app/(app)/[orgId]/integrations/components/PlatformIntegrations.tsx
@@ -34,7 +34,7 @@ import {
} from 'lucide-react';
import Image from 'next/image';
import Link from 'next/link';
-import { useParams, useSearchParams } from 'next/navigation';
+import { useParams, useRouter, useSearchParams } from 'next/navigation';
import { useEffect, useMemo, useRef, useState } from 'react';
import { toast } from 'sonner';
import {
@@ -48,6 +48,14 @@ import { TaskCard, TaskCardSkeleton } from './TaskCard';
const LOGO_TOKEN = 'pk_AZatYxV5QDSfWpRDaBxzRQ';
+// Providers that support employee sync via People > All
+const EMPLOYEE_SYNC_PROVIDERS = new Set([
+ 'google-workspace',
+ 'rippling',
+ 'jumpcloud',
+ 'ramp',
+]);
+
// Check if a provider needs variable configuration based on manifest's required variables
const providerNeedsConfiguration = (
requiredVariables: string[] | undefined,
@@ -78,6 +86,7 @@ interface PlatformIntegrationsProps {
export function PlatformIntegrations({ className, taskTemplates }: PlatformIntegrationsProps) {
const { orgId } = useParams<{ orgId: string }>();
+ const router = useRouter();
const searchParams = useSearchParams();
const { providers, isLoading: loadingProviders } = useIntegrationProviders(true);
const {
@@ -138,6 +147,18 @@ export function PlatformIntegrations({ className, taskTemplates }: PlatformInteg
};
const handleConnectDialogSuccess = () => {
+ // Prompt user to import employees for providers that support sync
+ if (connectingProviderInfo && EMPLOYEE_SYNC_PROVIDERS.has(connectingProviderInfo.id)) {
+ toast.info(`Import your ${connectingProviderInfo.name} users`, {
+ description:
+ 'Go to People to import and sync your team members.',
+ duration: 15000,
+ action: {
+ label: 'Go to People',
+ onClick: () => router.push(`/${orgId}/people/all`),
+ },
+ });
+ }
refreshConnections();
setConnectDialogOpen(false);
setConnectingProviderInfo(null);
@@ -264,6 +285,19 @@ export function PlatformIntegrations({ className, taskTemplates }: PlatformInteg
if (connection && provider) {
toast.success(`${provider.name} connected successfully!`);
+ // Prompt user to import employees for providers that support sync
+ if (EMPLOYEE_SYNC_PROVIDERS.has(providerSlug)) {
+ toast.info(`Import your ${provider.name} users`, {
+ description:
+ 'Go to People to import and sync your team members.',
+ duration: 15000,
+ action: {
+ label: 'Go to People',
+ onClick: () => router.push(`/${orgId}/people/all`),
+ },
+ });
+ }
+
// Set state first
setSelectedConnection(connection);
setSelectedProvider(provider);