Skip to content

Commit 6ab00a5

Browse files
tofikwestclaudeMarfuen
authored
feat(integrations): prompt user to import after Google Workspace connection (#2383)
Resolves ENG-114 Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Mariano Fuentes <marfuen98@gmail.com>
1 parent 9e00762 commit 6ab00a5

2 files changed

Lines changed: 163 additions & 2 deletions

File tree

apps/app/src/app/(app)/[orgId]/integrations/components/PlatformIntegrations.test.tsx

Lines changed: 128 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ vi.mock('next/link', () => ({
9292
// Mock next/navigation
9393
vi.mock('next/navigation', () => ({
9494
useParams: () => ({ orgId: 'org-1' }),
95+
useRouter: () => ({ push: vi.fn() }),
9596
useSearchParams: () => new URLSearchParams(),
9697
}));
9798

@@ -143,9 +144,10 @@ vi.mock('lucide-react', () => ({
143144

144145
// Mock sonner
145146
vi.mock('sonner', () => ({
146-
toast: { success: vi.fn(), error: vi.fn() },
147+
toast: { success: vi.fn(), error: vi.fn(), info: vi.fn() },
147148
}));
148149

150+
import { toast } from 'sonner';
149151
import { PlatformIntegrations } from './PlatformIntegrations';
150152

151153
const defaultProps = {
@@ -202,4 +204,129 @@ describe('PlatformIntegrations', () => {
202204
expect(screen.getByTestId('search-input')).toBeInTheDocument();
203205
});
204206
});
207+
208+
describe('Employee sync import prompt', () => {
209+
it('shows import prompt toast after Google Workspace OAuth callback', async () => {
210+
// Override mocks for this test to simulate OAuth callback
211+
const { useIntegrationProviders, useIntegrationConnections } = vi.mocked(
212+
await import('@/hooks/use-integration-platform'),
213+
);
214+
215+
vi.mocked(useIntegrationProviders).mockReturnValue({
216+
providers: [
217+
{
218+
id: 'google-workspace',
219+
name: 'Google Workspace',
220+
description: 'Google Workspace admin',
221+
category: 'Identity & Access',
222+
logoUrl: '/google.png',
223+
authType: 'oauth2',
224+
oauthConfigured: true,
225+
isActive: true,
226+
requiredVariables: [],
227+
mappedTasks: [],
228+
supportsMultipleConnections: false,
229+
},
230+
] as any,
231+
isLoading: false,
232+
error: undefined,
233+
refresh: vi.fn(),
234+
});
235+
236+
vi.mocked(useIntegrationConnections).mockReturnValue({
237+
connections: [
238+
{
239+
id: 'conn-1',
240+
providerSlug: 'google-workspace',
241+
status: 'active',
242+
variables: null,
243+
},
244+
] as any,
245+
isLoading: false,
246+
error: undefined,
247+
refresh: vi.fn(),
248+
});
249+
250+
// Mock useSearchParams to simulate OAuth callback
251+
const { useSearchParams: mockUseSearchParams } = vi.mocked(
252+
await import('next/navigation'),
253+
);
254+
vi.mocked(mockUseSearchParams).mockReturnValue(
255+
new URLSearchParams('success=true&provider=google-workspace') as any,
256+
);
257+
258+
setMockPermissions(ADMIN_PERMISSIONS);
259+
260+
render(<PlatformIntegrations {...defaultProps} />);
261+
262+
expect(toast.success).toHaveBeenCalledWith(
263+
'Google Workspace connected successfully!',
264+
);
265+
expect(toast.info).toHaveBeenCalledWith(
266+
'Import your Google Workspace users',
267+
expect.objectContaining({
268+
description: 'Go to People to import and sync your team members.',
269+
action: expect.objectContaining({ label: 'Go to People' }),
270+
}),
271+
);
272+
});
273+
274+
it('does not show import prompt for non-sync providers', async () => {
275+
// Override mocks for this test to simulate OAuth callback for a non-sync provider
276+
const { useIntegrationProviders, useIntegrationConnections } = vi.mocked(
277+
await import('@/hooks/use-integration-platform'),
278+
);
279+
280+
vi.mocked(useIntegrationProviders).mockReturnValue({
281+
providers: [
282+
{
283+
id: 'github',
284+
name: 'GitHub',
285+
description: 'Code hosting',
286+
category: 'Development',
287+
logoUrl: '/github.png',
288+
authType: 'oauth2',
289+
oauthConfigured: true,
290+
isActive: true,
291+
requiredVariables: [],
292+
mappedTasks: [],
293+
supportsMultipleConnections: false,
294+
},
295+
] as any,
296+
isLoading: false,
297+
error: undefined,
298+
refresh: vi.fn(),
299+
});
300+
301+
vi.mocked(useIntegrationConnections).mockReturnValue({
302+
connections: [
303+
{
304+
id: 'conn-2',
305+
providerSlug: 'github',
306+
status: 'active',
307+
variables: null,
308+
},
309+
] as any,
310+
isLoading: false,
311+
error: undefined,
312+
refresh: vi.fn(),
313+
});
314+
315+
const { useSearchParams: mockUseSearchParams } = vi.mocked(
316+
await import('next/navigation'),
317+
);
318+
vi.mocked(mockUseSearchParams).mockReturnValue(
319+
new URLSearchParams('success=true&provider=github') as any,
320+
);
321+
322+
setMockPermissions(ADMIN_PERMISSIONS);
323+
324+
render(<PlatformIntegrations {...defaultProps} />);
325+
326+
expect(toast.success).toHaveBeenCalledWith(
327+
'GitHub connected successfully!',
328+
);
329+
expect(toast.info).not.toHaveBeenCalled();
330+
});
331+
});
205332
});

apps/app/src/app/(app)/[orgId]/integrations/components/PlatformIntegrations.tsx

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import {
3434
} from 'lucide-react';
3535
import Image from 'next/image';
3636
import Link from 'next/link';
37-
import { useParams, useSearchParams } from 'next/navigation';
37+
import { useParams, useRouter, useSearchParams } from 'next/navigation';
3838
import { useEffect, useMemo, useRef, useState } from 'react';
3939
import { toast } from 'sonner';
4040
import {
@@ -48,6 +48,14 @@ import { TaskCard, TaskCardSkeleton } from './TaskCard';
4848

4949
const LOGO_TOKEN = 'pk_AZatYxV5QDSfWpRDaBxzRQ';
5050

51+
// Providers that support employee sync via People > All
52+
const EMPLOYEE_SYNC_PROVIDERS = new Set([
53+
'google-workspace',
54+
'rippling',
55+
'jumpcloud',
56+
'ramp',
57+
]);
58+
5159
// Check if a provider needs variable configuration based on manifest's required variables
5260
const providerNeedsConfiguration = (
5361
requiredVariables: string[] | undefined,
@@ -78,6 +86,7 @@ interface PlatformIntegrationsProps {
7886

7987
export function PlatformIntegrations({ className, taskTemplates }: PlatformIntegrationsProps) {
8088
const { orgId } = useParams<{ orgId: string }>();
89+
const router = useRouter();
8190
const searchParams = useSearchParams();
8291
const { providers, isLoading: loadingProviders } = useIntegrationProviders(true);
8392
const {
@@ -138,6 +147,18 @@ export function PlatformIntegrations({ className, taskTemplates }: PlatformInteg
138147
};
139148

140149
const handleConnectDialogSuccess = () => {
150+
// Prompt user to import employees for providers that support sync
151+
if (connectingProviderInfo && EMPLOYEE_SYNC_PROVIDERS.has(connectingProviderInfo.id)) {
152+
toast.info(`Import your ${connectingProviderInfo.name} users`, {
153+
description:
154+
'Go to People to import and sync your team members.',
155+
duration: 15000,
156+
action: {
157+
label: 'Go to People',
158+
onClick: () => router.push(`/${orgId}/people/all`),
159+
},
160+
});
161+
}
141162
refreshConnections();
142163
setConnectDialogOpen(false);
143164
setConnectingProviderInfo(null);
@@ -264,6 +285,19 @@ export function PlatformIntegrations({ className, taskTemplates }: PlatformInteg
264285
if (connection && provider) {
265286
toast.success(`${provider.name} connected successfully!`);
266287

288+
// Prompt user to import employees for providers that support sync
289+
if (EMPLOYEE_SYNC_PROVIDERS.has(providerSlug)) {
290+
toast.info(`Import your ${provider.name} users`, {
291+
description:
292+
'Go to People to import and sync your team members.',
293+
duration: 15000,
294+
action: {
295+
label: 'Go to People',
296+
onClick: () => router.push(`/${orgId}/people/all`),
297+
},
298+
});
299+
}
300+
267301
// Set state first
268302
setSelectedConnection(connection);
269303
setSelectedProvider(provider);

0 commit comments

Comments
 (0)