﻿# Framework Integrations

Use this guide to integrate `@product7/product7-js` in modern frontend frameworks.

This guide covers:

1. Next.js
2. React
3. Vue and Nuxt
4. Angular
5. Svelte and SvelteKit
6. Astro

## Before You Start

Install the SDK:

```bash
npm install @product7/product7-js
```

Prepare runtime values:

- `workspace` is required
- `boardName` is recommended for feedback widgets
- user identity should be passed to `identify()` after `init()`

Recommended integration rules:

- Initialize on the client side only.
- Always call `await sdk.init()` before creating widgets.
- Call `await sdk.identify(user)` after `init()` when you have a logged-in user.
- Prefer one shared SDK instance per app session.
- Destroy widgets and the SDK on teardown where appropriate.

### Shared Config Pattern

Keep workspace-level SDK config separate from user identity:

```ts
const sdkConfig = {
	workspace: 'your-workspace',
	boardName: 'feature-requests',
	debug: false,
};

const identifyPayload = {
	user_id: currentUser.id,
	email: currentUser.email,
	name: currentUser.name,
	custom_fields: {
		plan: currentUser.plan,
		role: currentUser.role,
	},
};
```

---

## 1. Next.js

### App Router

`app/providers.tsx`

```tsx
'use client';

import { useEffect, useRef } from 'react';
import { Product7 } from '@product7/product7-js';

export function Product7Provider() {
	const sdkRef = useRef<any>(null);
	const widgetRef = useRef<any>(null);

	useEffect(() => {
		let cancelled = false;

		(async () => {
			const sdk = new Product7({
				workspace: process.env.NEXT_PUBLIC_PRODUCT7_WORKSPACE!,
				boardName:
					process.env.NEXT_PUBLIC_PRODUCT7_BOARD_ID || 'feature-requests',
			});

			await sdk.init();
			await sdk.identify({
				user_id: 'user_123',
				email: 'user@example.com',
				name: 'Jane Doe',
			});

			if (cancelled) {
				sdk.destroy();
				return;
			}

			sdkRef.current = sdk;
			widgetRef.current = sdk.createFeedbackWidget({
				position: 'bottom-right',
			});
			widgetRef.current.mount();
		})();

		return () => {
			cancelled = true;
			widgetRef.current?.destroy();
			sdkRef.current?.destroy();
		};
	}, []);

	return null;
}
```

`app/layout.tsx`

```tsx
import { Product7Provider } from './providers';

export default function RootLayout({
	children,
}: {
	children: React.ReactNode;
}) {
	return (
		<html lang="en">
			<body>
				<Product7Provider />
				{children}
			</body>
		</html>
	);
}
```

### Pages Router

`pages/_app.tsx`

```tsx
import type { AppProps } from 'next/app';
import { useEffect } from 'react';
import { Product7 } from '@product7/product7-js';

export default function App({ Component, pageProps }: AppProps) {
	useEffect(() => {
		const sdk = new Product7({
			workspace: process.env.NEXT_PUBLIC_PRODUCT7_WORKSPACE!,
			boardName: 'feature-requests',
		});

		let widget: any;

		(async () => {
			await sdk.init();
			await sdk.identify({
				user_id: 'user_123',
				email: 'user@example.com',
			});

			widget = sdk.createFeedbackWidget({
				position: 'bottom-right',
			});
			widget.mount();
		})();

		return () => {
			widget?.destroy();
			sdk.destroy();
		};
	}, []);

	return <Component {...pageProps} />;
}
```

---

## 2. React

`src/App.tsx`

```tsx
import { useEffect, useRef } from 'react';
import { Product7 } from '@product7/product7-js';

export default function App() {
	const sdkRef = useRef<any>(null);
	const widgetRef = useRef<any>(null);

	useEffect(() => {
		let disposed = false;

		(async () => {
			const sdk = new Product7({
				workspace: import.meta.env.VITE_PRODUCT7_WORKSPACE,
				boardName: import.meta.env.VITE_PRODUCT7_BOARD_ID || 'feature-requests',
			});

			await sdk.init();
			await sdk.identify({
				user_id: 'user_123',
				email: 'user@example.com',
				name: 'Jane Doe',
			});

			if (disposed) {
				sdk.destroy();
				return;
			}

			sdkRef.current = sdk;
			widgetRef.current = sdk.createFeedbackWidget({
				position: 'bottom-right',
			});
			widgetRef.current.mount();
		})();

		return () => {
			disposed = true;
			widgetRef.current?.destroy();
			sdkRef.current?.destroy();
		};
	}, []);

	return <main>My App</main>;
}
```

---

## 3. Vue and Nuxt

### Vue

`src/App.vue`

```vue
<script setup lang="ts">
	import { onMounted, onUnmounted, ref } from 'vue';
	import { Product7 } from '@product7/product7-js';

	const sdk = ref<any>(null);
	const widget = ref<any>(null);

	onMounted(async () => {
		sdk.value = new Product7({
			workspace: import.meta.env.VITE_PRODUCT7_WORKSPACE,
			boardName: 'feature-requests',
		});

		await sdk.value.init();
		await sdk.value.identify({
			user_id: 'user_123',
			email: 'user@example.com',
			name: 'Jane Doe',
		});

		widget.value = sdk.value.createFeedbackWidget({
			position: 'bottom-right',
		});
		widget.value.mount();
	});

	onUnmounted(() => {
		widget.value?.destroy();
		sdk.value?.destroy();
	});
</script>
```

### Nuxt Plugin

`plugins/product7.client.ts`

```ts
import { Product7 } from '@product7/product7-js';

export default defineNuxtPlugin(async () => {
	const runtime = useRuntimeConfig();

	const sdk = new Product7({
		workspace: runtime.public.product7Workspace,
		boardName: runtime.public.product7BoardId || 'feature-requests',
	});

	await sdk.init();

	return {
		provide: {
			product7: sdk,
		},
	};
});
```

When your auth state is ready, identify the user:

```ts
const { $product7 } = useNuxtApp();

await $product7.identify({
	user_id: user.id,
	email: user.email,
	name: user.name,
});
```

### Nuxt Headless Composable

`composables/useFeedbackWidget.ts`

```ts
import { onMounted, onUnmounted, ref } from 'vue';

interface FeedbackWidgetOptions {
	boardName?: string;
	position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
	headless?: boolean;
}

export function useFeedbackWidget(options: FeedbackWidgetOptions = {}) {
	const widget = ref<any>(null);
	const isReady = ref(false);

	onMounted(async () => {
		const { $product7 } = useNuxtApp();

		widget.value = $product7.createFeedbackWidget({
			headless: true,
			...options,
		});
		widget.value.mount();
		isReady.value = true;
	});

	onUnmounted(() => {
		widget.value?.destroy();
	});

	return {
		isReady,
		open: () => widget.value?.open(),
		close: () => widget.value?.close(),
		toggle: () => widget.value?.toggle(),
	};
}
```

Usage:

```vue
<script setup>
	const { open, isReady } = useFeedbackWidget({
		boardName: 'feature-requests',
	});
</script>

<template>
	<button :disabled="!isReady" @click="open">Leave feedback</button>
</template>
```

---

## 4. Angular

`feedback.service.ts`

```ts
import { Injectable } from '@angular/core';
import { Product7 } from '@product7/product7-js';

@Injectable({ providedIn: 'root' })
export class FeedbackService {
	private sdk: any = null;
	private feedbackWidget: any = null;

	async init(user: { id: string; email: string; name: string }) {
		if (this.sdk) return;

		this.sdk = new Product7({
			workspace: 'your-workspace',
			boardName: 'feature-requests',
		});

		await this.sdk.init();
		await this.sdk.identify({
			user_id: user.id,
			email: user.email,
			name: user.name,
		});
	}

	mountFeedback() {
		if (!this.sdk || this.feedbackWidget) return;

		this.feedbackWidget = this.sdk.createFeedbackWidget({
			position: 'bottom-right',
		});
		this.feedbackWidget.mount();
	}

	destroy() {
		this.feedbackWidget?.destroy();
		this.feedbackWidget = null;
		this.sdk?.destroy();
		this.sdk = null;
	}
}
```

---

## 5. Svelte and SvelteKit

### Svelte

`src/App.svelte`

```svelte
<script lang="ts">
	import { onMount } from 'svelte';
	import { Product7 } from '@product7/product7-js';

	let sdk: any = null;
	let widget: any = null;

	onMount(() => {
		let disposed = false;

		(async () => {
			sdk = new Product7({
				workspace: 'your-workspace',
				boardName: 'feature-requests',
			});

			await sdk.init();
			await sdk.identify({
				user_id: 'user_123',
				email: 'user@example.com',
				name: 'Jane Doe',
			});

			if (disposed) return;

			widget = sdk.createFeedbackWidget({
				position: 'bottom-right',
			});
			widget.mount();
		})();

		return () => {
			disposed = true;
			widget?.destroy();
			sdk?.destroy();
		};
	});
</script>
```

### SvelteKit

`src/routes/+layout.svelte`

```svelte
<script lang="ts">
	import { browser } from '$app/environment';
	import { onMount } from 'svelte';
	import { Product7 } from '@product7/product7-js';

	let sdk: any;
	let widget: any;

	onMount(() => {
		if (!browser) return;

		(async () => {
			sdk = new Product7({
				workspace: 'your-workspace',
				boardName: 'feature-requests',
			});

			await sdk.init();
			await sdk.identify({
				user_id: 'user_123',
				email: 'user@example.com',
			});

			widget = sdk.createFeedbackWidget({
				position: 'bottom-right',
			});
			widget.mount();
		})();

		return () => {
			widget?.destroy();
			sdk?.destroy();
		};
	});
</script>
```

---

## 6. Astro

Astro renders on the server by default, so initialize Product7 from a client-loaded component.

`src/components/Product7Widget.astro`

```astro
<script>
	import { Product7 } from '@product7/product7-js';

	let sdk;
	let widget;

	(async () => {
		sdk = new Product7({
			workspace: 'your-workspace',
			boardName: 'feature-requests',
		});

		await sdk.init();
		await sdk.identify({
			user_id: 'user_123',
			email: 'user@example.com',
			name: 'Jane Doe',
		});

		widget = sdk.createFeedbackWidget({
			position: 'bottom-right',
		});
		widget.mount();
	})();
</script>
```

---

## Adding More Widgets

Use the same SDK instance for feedback, survey, web chat, and changelog.

```ts
const feedback = sdk.createFeedbackWidget({
	headless: true,
	boardName: 'feature-requests',
});
feedback.mount();

const survey = sdk.createSurveyWidget({
	surveyType: 'nps',
	position: 'center',
	ratingScale: 5,
	showSubmitButton: true,
});
survey.mount();
survey.open();

const liveChat = sdk.createLiveChatWidget({
	position: 'bottom-right',
	teamName: 'Support Team',
	enableHelp: true,
	enableChangelog: true,
});
liveChat.mount();

const changelog = sdk.createChangelogWidget({
	position: 'bottom-left',
	triggerText: "What's New",
	showBadge: true,
});
changelog.mount();
```

---

## Troubleshooting

- `SDK must be initialized before creating widgets`
  Call `await sdk.init()` before any widget factory method.
- Widget not visible
  Make sure you call `widget.mount()` for mountable widgets.
- `window is not defined` or `document is not defined`
  Move SDK code to client-only lifecycle hooks or client-only components.
- Duplicate widget instances
  Cache widget references and avoid remounting in repeated renders.
- Web chat shows the launcher when you want only programmatic control
  Use `headless: true` when creating the web chat widget.
