Skip to content

Commit 276c133

Browse files
Merge pull request #11 from pipedrive/AINATIVEM-44
AINATIVEM-44 app extensions: ComposeBuilder, panel SDK demo, env-driven config
2 parents 2daf8c3 + 4c93e67 commit 276c133

5 files changed

Lines changed: 43 additions & 15 deletions

File tree

src/generators/node/appExtensions.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,9 +153,9 @@ describe('generateAppExtensions', () => {
153153
await generateAppExtensions(tmpDir, options);
154154

155155
const app = await readFile(join(tmpDir, 'frontend/app-extension-ui/src/Panel.tsx'), 'utf-8');
156-
expect(app).not.toContain('@pipedrive/app-extensions-sdk');
157-
expect(app).not.toContain('OPEN_MODAL');
158156
expect(app).not.toContain('CLOSE_MODAL');
157+
expect(app).not.toContain('CUSTOM_MODAL');
158+
expect(app).not.toContain('VITE_CUSTOM_MODAL_ACTION_ID');
159159
expect(app).not.toContain('Open modal');
160160
expect(app).not.toContain('Close modal');
161161
});

src/generators/node/appExtensions/frontend.ts

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ function viteConfigContent(): string {
3939
export default defineConfig({
4040
root,
4141
base: '/extensions/',
42+
// Load .env from the project root so VITE_* variables are available
43+
envDir: '../../',
4244
plugins: [react()],
4345
build: {
4446
outDir: 'dist',
@@ -169,15 +171,14 @@ function frontendTsConfig(): Record<string, unknown> {
169171
}
170172

171173
function panelComponentContent(hasModal: boolean): string {
172-
const sdkImportLine = hasModal ? "import { Command, Modal } from '@pipedrive/app-extensions-sdk';" : '';
173-
const runSdkActionDestructure = hasModal ? ', runSdkAction' : '';
174174
const openModalHandler = hasModal
175175
? dedent`
176176
async function openCustomModal(): Promise<void> {
177-
await runSdkAction('Custom modal opened', (client) =>
177+
await runSdkAction('Modal opened', (client) =>
178178
client.execute(Command.OPEN_MODAL, {
179179
type: Modal.CUSTOM_MODAL,
180-
action_id: 'custom-modal',
180+
// Replace with your Custom Modal's "Extension identifier" from Marketplace Developer Hub → App Extensions
181+
action_id: import.meta.env.VITE_CUSTOM_MODAL_ACTION_ID,
181182
data: { source: 'panel' },
182183
}),
183184
);
@@ -192,11 +193,10 @@ function panelComponentContent(hasModal: boolean): string {
192193
`
193194
: '';
194195

195-
const sdkImportPrefix = sdkImportLine ? sdkImportLine + '\n' : '';
196-
197196
return dedent`
198197
import { useEffect } from 'react';
199-
${sdkImportPrefix}import { usePipedriveSdk } from '../shared/pipedriveSdk';
198+
import { Command, Modal } from '@pipedrive/app-extensions-sdk';
199+
import { usePipedriveSdk } from '../shared/pipedriveSdk';
200200
201201
function formatQueryValue(key: string, value: string): string {
202202
return /token|secret|code/i.test(key) ? 'Present' : value;
@@ -209,14 +209,27 @@ function panelComponentContent(hasModal: boolean): string {
209209
}
210210
211211
export default function Panel() {
212-
const { context, status, theme, visibility, pageState, lastAction, signedTokenPreview, isReady${runSdkActionDestructure}, actions } =
212+
const { context, status, theme, visibility, pageState, lastAction, signedTokenPreview, isReady, runSdkAction, actions } =
213213
usePipedriveSdk('panel');
214214
const queryEntries = Object.entries(context.query);
215215
216216
useEffect(() => {
217217
document.title = 'Custom Panel';
218218
}, []);
219219
220+
async function logActivity(): Promise<void> {
221+
const dealId = context.query.selectedIds ? parseInt(context.query.selectedIds) : undefined;
222+
await runSdkAction('Activity logged', (client) =>
223+
client.execute(Command.OPEN_MODAL, {
224+
type: Modal.ACTIVITY,
225+
prefill: {
226+
subject: 'Follow-up',
227+
...(dealId ? { deal: dealId } : {}),
228+
},
229+
}),
230+
);
231+
}
232+
220233
${openModalHandler}
221234
222235
if (!isReady && status !== 'Local preview') {
@@ -263,14 +276,17 @@ function panelComponentContent(hasModal: boolean): string {
263276
</section>
264277
265278
<section className="toolbar" aria-label="SDK actions">
279+
<button type="button" disabled={!isReady} onClick={logActivity}>
280+
Log activity
281+
</button>
266282
<button type="button" disabled={!isReady} onClick={actions.showSnackbar}>
267283
Snackbar
268284
</button>
269285
<button type="button" className="secondary" disabled={!isReady} onClick={actions.showConfirmation}>
270286
Confirm
271287
</button>
272288
<button type="button" className="ghost" disabled={!isReady} onClick={actions.resize}>
273-
Resize panel
289+
{actions.isExpanded ? 'Collapse panel' : 'Expand panel'}
274290
</button>
275291
<button type="button" className="ghost" disabled={!isReady} onClick={actions.getSignedToken}>
276292
Get token
@@ -400,7 +416,7 @@ function modalComponentContent(): string {
400416
Confirm
401417
</button>
402418
<button type="button" className="ghost" disabled={!isReady} onClick={actions.resize}>
403-
Resize modal
419+
{actions.isExpanded ? 'Collapse modal' : 'Expand modal'}
404420
</button>
405421
<button type="button" className="ghost" disabled={!isReady} onClick={actions.getSignedToken}>
406422
Get token

src/generators/node/appExtensions/sdk.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -191,10 +191,18 @@ export function sdkWrapperContent(): string {
191191
if (response) setLastAction(response.confirmed ? 'Confirmed' : 'Cancelled');
192192
}, [runSdkAction]);
193193
194+
const [isExpanded, setIsExpanded] = useState(false);
195+
194196
const resize = useCallback(async () => {
195-
const size = surface === 'modal' ? { height: 480, width: 720 } : { height: 420 };
196-
await runSdkAction('Resize requested', (client) => client.execute(Command.RESIZE, size));
197-
}, [runSdkAction, surface]);
197+
const nextExpanded = !isExpanded;
198+
const size = surface === 'modal'
199+
? (nextExpanded ? { height: 480, width: 720 } : { height: 420, width: 640 })
200+
: (nextExpanded ? { height: 420 } : { height: 360 });
201+
await runSdkAction(nextExpanded ? 'Expanded' : 'Collapsed', (client) =>
202+
client.execute(Command.RESIZE, size),
203+
);
204+
setIsExpanded(nextExpanded);
205+
}, [runSdkAction, surface, isExpanded]);
198206
199207
const getSignedToken = useCallback(async () => {
200208
const response = await runSdkAction('Signed token received', (client) => client.execute(Command.GET_SIGNED_TOKEN));
@@ -220,6 +228,7 @@ export function sdkWrapperContent(): string {
220228
showConfirmation,
221229
resize,
222230
getSignedToken,
231+
isExpanded,
223232
},
224233
};
225234
}

src/generators/node/database.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,7 @@ function buildAppExtensionUiService(): ComposeService {
573573
build: { context: '.', dockerfile: 'Dockerfile.app-extension-ui' },
574574
user: 'root',
575575
command: nodeVolumeCommand(`${quietInstallCommand()} && npm run dev:frontend`),
576+
env_file: ['.env'],
576577
environment: { CHOKIDAR_USEPOLLING: 'true' },
577578
ports: ['5173:5173'],
578579
volumes: ['./package.json:/app/package.json:ro', 'app_extension_ui_node_modules:/app/node_modules'],

src/generators/node/projectBuilder.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,8 @@ function appExtensionEnvExample(options: GeneratorOptions): string {
226226
'# Custom modal iframe URLs:',
227227
'# Local: https://<your-vite-tunnel>/extensions/modal',
228228
'# Production: https://<your-backend-domain>/extensions/modal',
229+
'VITE_CUSTOM_MODAL_ACTION_ID=',
230+
'# Paste the "Extension identifier" from Marketplace Developer Hub → App Extensions',
229231
);
230232
}
231233

0 commit comments

Comments
 (0)