diff --git a/.env.local.example b/.env.local.example index 2fa613e3e..9872e55dd 100644 --- a/.env.local.example +++ b/.env.local.example @@ -14,3 +14,6 @@ ANTHROPIC_API_KEY= NEXT_PUBLIC_ALGOLIA_APP_ID= NEXT_PUBLIC_ALGOLIA_SEARCH_API_KEY= NEXT_PUBLIC_ALGOLIA_INDEX_NAME= + +# Required for Contact Sales form submission (n8n → Attio pipeline) +NEXT_PUBLIC_ATTIO_WEBHOOK_URL= diff --git a/app/en/references/auth-providers/canva/page.mdx b/app/en/references/auth-providers/canva/page.mdx new file mode 100644 index 000000000..f4c7a60ad --- /dev/null +++ b/app/en/references/auth-providers/canva/page.mdx @@ -0,0 +1,324 @@ +import { Tabs, Callout, Steps } from "nextra/components"; + +# Canva + +The Canva auth provider enables tools and agents to call [Canva APIs](https://developers.canva.com/docs/rest-api/) on behalf of a user using OAuth 2.0 authentication. + + + Want to quickly get started with Canva in your agent or AI app? The pre-built + [Arcade Canva MCP Server](/resources/integrations/development/canva) is what you + want! + + +### What's documented here + +This page describes how to use and configure Canva auth with Arcade. + +This auth provider is used by: + +- The [Arcade Canva MCP Server](/resources/integrations/development/canva), which provides pre-built tools for interacting with Canva +- Your [app code](#using-canva-auth-in-app-code) that needs to call the Canva API +- Or, your [custom tools](#using-canva-auth-in-custom-tools) that need to call the Canva API + +### Required scopes for the Canva MCP Server + +If you're using the [Arcade Canva MCP Server](/resources/integrations/development/canva), you'll need to configure these scopes based on which tools you plan to use: + +- `file_content:read` - File structure, pages, nodes, and image exports +- `library_content:read` - Published components, styles, and component sets from files +- `team_library_content:read` - Team library content +- `library_assets:read` - Individual component, style, and component set metadata +- `file_comments:read` / `file_comments:write` - Reading and creating comments +- `current_user:read` - User profile information +- `projects:read` - Team projects and files (**requires private OAuth app**) + + + The `projects:read` scope is **only available in private Canva OAuth apps**. + If you need to access team projects and files, you must create a private OAuth + app through your Canva organization. + + +For detailed descriptions of all available Canva OAuth scopes, refer to the [Canva OAuth Scopes documentation](https://developers.canva.com/docs/rest-api/scopes/). + +## Configuring Canva auth + + + When using your own app credentials, make sure you configure your project to + use a [custom user + verifier](/guides/user-facing-agents/secure-auth-production#build-a-custom-user-verifier). + Without this, your end-users will not be able to use your app or agent in + production. + + +In a production environment, you will most likely want to use your own Canva app credentials. This way, your users will see your application's name requesting permission. + +Before showing how to configure your Canva app credentials, let's go through the steps to create a Canva app. + +### Create a Canva app + +To integrate with Canva's API, you'll need to set up OAuth 2.0 authentication by creating an app in the Canva Developer Portal: + + + +#### Access the Canva Developer Portal + +Navigate to the [Canva Developer Portal](https://www.canva.com/developers/) and sign in with your existing Canva credentials or create a new account. + +#### Create a new app + +1. Once logged in, go to your developer dashboard and select "My Apps" +2. Click on "Create a new app" +3. Fill in the required details: + - **App Name**: Choose a descriptive name for your application + - **Website**: Provide the URL of your application's website + - **App Logo**: Upload a 100x100px PNG image representing your app + +#### Set up OAuth configuration + +1. After creating your app, you'll receive a `client_id` and `client_secret` +2. Set the redirect URI to the redirect URL generated by Arcade (see configuration section below) +3. Configure the required scopes for your application based on the tools you need: + - `file_content:read` - Read access to file content and structure + - `library_content:read` - Read access to published library content + - `team_library_content:read` - Read access to team library content + - `library_assets:read` - Read access to individual library assets + - `file_comments:read` - Read access to file comments + - `file_comments:write` - Write access to file comments + - `current_user:read` - Read access to user profile + - `projects:read` - Read access to team projects (private apps only) + +For a complete list of available scopes, refer to the [Canva OAuth Scopes documentation](https://developers.canva.com/docs/rest-api/scopes/) + + + +For detailed instructions, refer to Canva's official [Authentication documentation](https://developers.canva.com/docs/rest-api/authentication/). + +Next, add the Canva app to Arcade. + +## Configuring your own Canva Auth Provider in Arcade + + + + +### Configure Canva Auth Using the Arcade Dashboard GUI + + + +#### Access the Arcade Dashboard + +To access the Arcade Cloud dashboard, go to [api.arcade.dev/dashboard](https://api.arcade.dev/dashboard). If you are self-hosting, by default the dashboard will be available at http://localhost:9099/dashboard. Adjust the host and port number to match your environment. + +#### Navigate to the OAuth Providers page + +- Under the **Connections** section of the Arcade Dashboard left-side menu, click **Connected Apps**. +- Click **Add OAuth Provider** in the top right corner. +- Select the **OAuth 2.0** tab at the top. + +#### Enter the provider details + +- Choose a unique **ID** for your provider (e.g. "canva"). +- Optionally enter a **Description**. +- Enter the **Client ID** and **Client Secret** from your Canva app. +- Configure the OAuth 2.0 endpoints: + - **Authorization URL**: `https://www.canva.com/oauth` + - **Token URL**: `https://api.canva.com/v1/oauth/token` + - **Scope Delimiter**: ` ` (space) + - **Use PKCE**: Enabled (S256) +- Note the **Redirect URL** generated by Arcade. This must be set as your Canva app's redirect URI. + +#### Create the provider + +Hit the **Create** button and the provider will be ready to be used. + + + + + + + +### Configure Canva Auth Using Configuration File + + + This method is only available when you are [self-hosting the + engine](/guides/deployment-hosting/on-prem + + + + +#### Set environment variables + +Set the following environment variables: + +```bash +export CANVA_CLIENT_ID="" +export CANVA_CLIENT_SECRET="" +``` + +Or, you can set these values in a `.env` file: + +```bash +CANVA_CLIENT_ID="" +CANVA_CLIENT_SECRET="" +``` + +#### Edit the Engine configuration + +Edit the `engine.yaml` file and add a new item to the `auth.providers` section: + +```yaml +auth: + providers: + - id: canva + description: Canva OAuth 2.0 provider + enabled: true + type: oauth2 + client_id: ${env:CANVA_CLIENT_ID} + client_secret: ${env:CANVA_CLIENT_SECRET} + oauth2: + scope_delimiter: " " + use_pkce: true + pkce_code_challenge_method: S256 + authorize_request: + endpoint: "https://www.canva.com/oauth" + params: + response_type: code + client_id: "{{client_id}}" + redirect_uri: "{{redirect_uri}}" + scope: "{{scopes}} {{existing_scopes}}" + state: "{{state}}" + token_request: + endpoint: "https://api.canva.com/v1/oauth/token" + auth_method: client_secret_basic + params: + grant_type: authorization_code + redirect_uri: "{{redirect_uri}}" + request_content_type: application/x-www-form-urlencoded + response_content_type: application/json + refresh_request: + endpoint: "https://api.canva.com/v1/oauth/token" + auth_method: client_secret_basic + params: + grant_type: refresh_token + refresh_token: "{{refresh_token}}" + request_content_type: application/x-www-form-urlencoded + response_content_type: application/json +``` + + + **Note on `projects:read` scope:** If you need access to the `projects:read` + scope for team projects and files navigation, you must create a **private + Canva OAuth app**. This scope is not available in public OAuth apps. Learn + more in the [Canva OAuth Scopes + documentation](https://developers.canva.com/docs/rest-api/scopes/). + + + + + + + +When you use tools that require Canva auth using your Arcade account credentials, Arcade will automatically use this Canva OAuth provider. If you have multiple Canva providers, see [using multiple auth providers of the same type](/references/auth-providers#using-multiple-providers-of-the-same-type) for more information. + +## Using Canva auth in app code + +Use the Canva auth provider in your own agents and AI apps to get a user token for the Canva API. See [authorizing agents with Arcade](/get-started/about-arcade) to understand how this works. + +Use `client.auth.start()` to get a user token for the Canva API: + + + + +```python {8-12} +from arcadepy import Arcade + +client = Arcade() # Automatically finds the `ARCADE_API_KEY` env variable + +user_id = "{arcade_user_id}" + +# Start the authorization process +auth_response = client.auth.start( + user_id=user_id, + provider="canva", + scopes=["file_content:read", "file_comments:write"] +) + +if auth_response.status != "completed": + print("Please complete the authorization challenge in your browser:") + print(auth_response.url) + +# Wait for the authorization to complete +auth_response = client.auth.wait_for_completion(auth_response) + +token = auth_response.context.token +# Do something interesting with the token... +``` + + + + + +```javascript {8-11} +import { Arcade } from "@arcadeai/arcadejs"; + +const client = new Arcade(); + +const userId = "{arcade_user_id}"; + +// Start the authorization process +const authResponse = await client.auth.start(userId, "canva", [ + "file_content:read", + "file_comments:write", +]); + +if (authResponse.status !== "completed") { + console.log("Please complete the authorization challenge in your browser:"); + console.log(authResponse.url); +} + +// Wait for the authorization to complete +authResponse = await client.auth.waitForCompletion(authResponse); + +const token = authResponse.context.token; +// Do something interesting with the token... +``` + + + + + +## Using Canva auth in custom tools + +You can use the pre-built [Arcade Canva MCP Server](/resources/integrations/development/canva) to quickly build agents and AI apps that interact with Canva. + +If the pre-built tools in the Canva MCP Server don't meet your needs, you can author your own [custom tools](/guides/create-tools/tool-basics/build-mcp-server) that interact with the Canva API. + +Use the `Canva()` auth class to specify that a tool requires authorization with Canva. The `context.authorization.token` field will be automatically populated with the user's Canva token: + +```python {5,8} +from typing import Annotated + +import httpx +from arcade_tdk import ToolContext, tool +from arcade_tdk.auth import Canva + + +@tool(requires_auth=Canva(scopes=["file_content:read"])) +async def get_canva_file( + context: ToolContext, + file_key: Annotated[str, "The Canva file key to retrieve."], +) -> Annotated[dict, "The Canva file data."]: + """ + Retrieve a Canva file by its key. + """ + url = f"https://api.canva.com/v1/files/{file_key}" + headers = { + "Authorization": f"Bearer {context.authorization.token}", + } + + async with httpx.AsyncClient() as client: + response = await client.get(url, headers=headers) + response.raise_for_status() + return dict(response.json()) +``` + +For a complete list of available Canva OAuth scopes and their descriptions, refer to the [Canva OAuth Scopes documentation](https://developers.canva.com/docs/rest-api/scopes/). diff --git a/app/en/resources/contact-us/contact-cards.tsx b/app/en/resources/contact-us/contact-cards.tsx index 292ae1ca9..0041a0a90 100644 --- a/app/en/resources/contact-us/contact-cards.tsx +++ b/app/en/resources/contact-us/contact-cards.tsx @@ -1,30 +1,269 @@ "use client"; import { + Button, Dialog, DialogContent, + DialogHeader, + DialogTitle, Discord, Github, + Input, + Textarea, } from "@arcadeai/design-system"; -import { HeartPulse, Mail, Shield, Users } from "lucide-react"; +import { + AlertOctagon, + CheckCircle, + HeartPulse, + Mail, + Shield, + Users, +} from "lucide-react"; import posthog from "posthog-js"; -import { useEffect, useRef, useState } from "react"; +import { useState } from "react"; +import { useForm } from "react-hook-form"; import { QuickStartCard } from "../../../_components/quick-start-card"; +const WEBHOOK_URL = process.env.NEXT_PUBLIC_ATTIO_WEBHOOK_URL; + +function getUtmParams(): Record { + if (typeof window === "undefined") { + return {}; + } + const params = new URLSearchParams(window.location.search); + const utms: Record = {}; + for (const key of [ + "utm_source", + "utm_medium", + "utm_campaign", + "utm_content", + ]) { + const value = params.get(key); + if (value) { + utms[key] = value; + } + } + return utms; +} + +function collectFields(data: FormValues): Record { + const fields: Record = {}; + for (const [key, value] of Object.entries(data)) { + if (key !== "website" && value.trim()) { + fields[key] = value.trim(); + } + } + return fields; +} + +async function fireHoneypot(): Promise { + if (!WEBHOOK_URL) { + return; + } + try { + await fetch(WEBHOOK_URL, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ _hp: true }), + }); + } catch { + // Swallow errors for spam submissions + } +} + +async function submitToAttio( + fields: Record +): Promise<{ success: boolean; error?: string }> { + if (!WEBHOOK_URL) { + return { success: false, error: "Webhook URL not configured" }; + } + + const payload = { + submission_id: crypto.randomUUID(), + form_type: "contact_sales", + fields, + context: { + pageUri: window.location.href, + pageName: "Contact Sales", + timestamp: new Date().toISOString(), + ...getUtmParams(), + }, + }; + + const response = await fetch(WEBHOOK_URL, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(payload), + }); + + if (response.ok) { + return { success: true }; + } + return { + success: false, + error: `HTTP ${response.status}: ${response.statusText}`, + }; +} + +type FormValues = { + firstname: string; + lastname: string; + email: string; + company: string; + message: string; + website: string; +}; + +function ContactSalesForm({ onSuccess }: { onSuccess: () => void }) { + const [submitError, setSubmitError] = useState(""); + const { + register, + handleSubmit, + formState: { isSubmitting }, + } = useForm(); + + const onSubmit = async (data: FormValues) => { + setSubmitError(""); + + // Honeypot — silently succeed for bots + if (data.website) { + await fireHoneypot(); + onSuccess(); + return; + } + + const result = await submitToAttio(collectFields(data)); + + if (result.success) { + posthog.capture("contact_sales_form_submitted", { + form_type: "contact_sales", + page: "Contact Sales", + source: "contact_us_page", + }); + onSuccess(); + } else { + posthog.capture("contact_sales_form_submit_failed", { + form_type: "contact_sales", + page: "Contact Sales", + error: result.error, + }); + setSubmitError("Oops! Something went wrong. Please try again."); + } + }; + + return ( +
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+
+ +