### Integration Test Example with Vitest and Supabase Utilities Source: https://github.com/angelod1as/positiv/blob/main/CLAUDE.md Illustrates an integration test setup for database operations using Vitest. It leverages custom test utilities for managing test data and database connections with Supabase. Key functions include `setupIntegrationTest`, `cleanupAfterTest`, `createTestProfile`, and `createTestEvent`. This setup ensures a clean database state before and after each test. ```typescript import { describe, expect, it, beforeEach, afterEach } from "vitest" import { setupIntegrationTest, cleanupAfterTest } from "~/test/integration-setup" import { createTestProfile, createTestEvent } from "~/test/db-test-utils" describe("Database Function - Integration Tests", () => { const { tracker, kysely } = setupIntegrationTest() beforeEach(async () => { tracker.clear() // Clear existing test data if needed }) afterEach(async () => { await cleanupAfterTest(tracker, kysely) }) it("should perform database operation", async () => { const event = await createTestEvent(tracker, kysely, { title: "Test Event", // ... other fields }) // Test your database function const result = await myDatabaseFunction(event.id) expect(result).toBeDefined() }) }) ``` -------------------------------- ### Build and Start Application Source: https://github.com/angelod1as/positiv/blob/main/README.md Commands to build the Positiv application for deployment and to start the Node.js server. This process is suitable for deployment on any hosting service supporting Node.js applications. ```bash pnpm build ``` ```bash pnpm start ``` -------------------------------- ### Install Dependencies (Bash) Source: https://github.com/angelod1as/positiv/blob/main/README.md Installs project dependencies using the pnpm package manager. This command should be run after cloning the repository. ```bash pnpm install ``` -------------------------------- ### Start Development Server (Bash) Source: https://github.com/angelod1as/positiv/blob/main/README.md Starts the development server for the Positiv project. This command allows for real-time development and testing of features. ```bash pnpm dev ``` -------------------------------- ### Environment Variables Example (Bash) Source: https://github.com/angelod1as/positiv/blob/main/docs/payment-system.md This bash script provides an example of how to set up environment variables for the Positiv project. It includes variables for feature flags, Asaas integration credentials (API key, webhook token, environment), application URL, and a secret for cron job authentication. These variables are crucial for configuring the application's behavior and integrations. ```bash # Feature Flag ENABLE_PAYMENT_SYSTEM=false # Asaas Integration ASAAS_API_KEY=your_api_key ASAAS_WEBHOOK_TOKEN=your_webhook_token ASAAS_ENVIRONMENT=sandbox # or 'production' # Application APP_URL=https://positiv.com.br # Cron Jobs CRON_SECRET=random_secret_for_cron_auth ``` -------------------------------- ### Set Up Local Supabase (Bash) Source: https://github.com/angelod1as/positiv/blob/main/README.md Commands for setting up and managing a local Supabase instance using Docker and the Supabase CLI. This involves initializing, starting, resetting the database, and configuring environment variables. ```bash supabase init supabase start supabase db reset ``` -------------------------------- ### Set Up Environment Variables (Bash) Source: https://github.com/angelod1as/positiv/blob/main/README.md Copies the example environment file to a new file named '.env' and instructs the user to edit it with their local Supabase credentials and other necessary variables. This is crucial for local development. ```bash cp .env.example .env ``` -------------------------------- ### Page Object Model Example in TypeScript Source: https://github.com/angelod1as/positiv/blob/main/e2e/README.md An example implementation of the Page Object Model (POM) pattern in TypeScript, extending a BasePage class. It defines page elements and interaction methods for better test maintainability. ```typescript export class ExamplePage extends BasePage { readonly submitButton: Locator constructor(page: Page) { super(page) this.submitButton = page.getByRole('button', { name: 'Submit' }) } async submit(): Promise { await this.submitButton.click() } } ``` -------------------------------- ### Install AG Grid Dependencies Source: https://github.com/angelod1as/positiv/blob/main/PLAN.md Installs the necessary AG Grid React and Community packages using pnpm. This is the first step in integrating AG Grid into the project. ```bash cd positiv pnpm add ag-grid-react ag-grid-community ``` -------------------------------- ### Sequential Login Flow Test (TypeScript) Source: https://github.com/angelod1as/positiv/blob/main/e2e/README.md An example of a well-structured E2E test in TypeScript using Playwright. This test demonstrates a sequential login flow, including validation for empty fields, invalid credentials, and successful login, followed by assertions on page navigation. It highlights composing tests for realistic user journeys. ```typescript test('complete login flow from validation to logout', async ({ page }) => { // Test empty fields await submitButton.click() await expect(emailError).toBeVisible() // Test invalid credentials (reusing same form) await emailInput.fill('wrong@example.com') await passwordInput.fill('wrong') await submitButton.click() await expect(errorAlert).toBeVisible() // Test successful login (clearing and reusing form) await emailInput.clear() await passwordInput.clear() await emailInput.fill(validEmail) await passwordInput.fill(validPassword) await submitButton.click() await expect(page).toHaveURL('/dashboard') // Continue with session tests... }) ``` -------------------------------- ### Start Mailhog for Local Email Testing Source: https://github.com/angelod1as/positiv/blob/main/CLAUDE.md This command starts Mailhog, a tool used for local email testing within the project. It allows developers to inspect emails sent during development without needing a real email server. ```bash # Email Testing pnpm email:test # Start Mailhog for local email testing ``` -------------------------------- ### Local Development Setup Commands (Bash) Source: https://github.com/angelod1as/positiv/blob/main/docs/payment-system.md This bash script outlines the steps for setting up the Positiv project in a local development environment. It includes commands for copying environment variables, resetting the Supabase database, generating database types, and enabling the payment system feature flag. These steps are essential for a smooth local development workflow. ```bash cp .env.example .env # Fill in Asaas credentials pnpm supabase db reset pnpm db:types ENABLE_PAYMENT_SYSTEM=true ``` -------------------------------- ### Vitest Integration Test Example for Demographics Source: https://context7.com/angelod1as/positiv/llms.txt An example of an integration test using Vitest, demonstrating how to set up tests with the integration test utilities, create test data for events and profiles, and assert demographic calculation results. It covers the process of creating participants, calculating demographics, and storing a snapshot of the results. ```typescript // Integration test example import { describe, expect, it, beforeEach, afterEach } from "vitest" describe("Demographics Integration Tests", () => { const { tracker, kysely } = setupIntegrationTest() beforeEach(async () => { tracker.clear() }) afterEach(async () => { await cleanupAfterTest(tracker, kysely) }) it("should calculate and store demographics", async () => { const event = await createTestEvent(tracker, kysely, { title: "Demo Event" }) const profile = await createTestProfile(tracker, kysely, { date_of_birth: "1990-01-01", gender: ["cis woman"], is_veteran: false }) // Create participant const participant = await kysely .insertInto("event_participants") .values({ event_id: event.id, profile_id: profile.id, application_status: "finalised" }) .returningAll() .executeTakeFirstOrThrow() tracker.track("event_participants", participant.id) // Calculate demographics const rows = await getDemographicsData(event.id) const demographics = calculateDemographics(rows) expect(demographics.total).toBe(1) expect(demographics.veteran.no).toBe(100) // Store snapshot const result = await upsertEventDemographicsSnapshot({ eventId: event.id, demographics }) expect(result.success).toBe(true) tracker.track("event_demographics_history", result.data.id) }) }) ``` -------------------------------- ### Manage Architectural Decisions with Log4Brains Source: https://github.com/angelod1as/positiv/blob/main/README.md Instructions for installing and using Log4Brains to manage Architectural Decision Records (ADRs) for the project. ADRs are recommended for significant project decisions. ```bash pnpm install -g log4brains ``` ```bash log4brains preview ``` ```bash log4brains adr new ``` -------------------------------- ### TypeScript Integration Test Setup and Cleanup Source: https://context7.com/angelod1as/positiv/llms.txt Sets up database-backed integration tests with mechanisms for tracking and cleaning up test data. It uses Kysely for type-safe database operations and Supabase for authentication management. This module facilitates reliable testing by ensuring a clean state before each test and proper data removal afterward. ```typescript // app/test/integration-setup.ts import { Kysely } from "kysely" import { createClient } from "@supabase/supabase-js" class TestDataTracker { private tables: Map> = new Map() private authEmails: Set = new Set() track(table: string, id: string) { if (!this.tables.has(table)) { this.tables.set(table, new Set()) } this.tables.get(table)!.add(id) } trackAuthUser(email: string) { this.authEmails.add(email) } clear() { this.tables.clear() this.authEmails.clear() } getTables() { return this.tables } getAuthEmails() { return this.authEmails } } export function setupIntegrationTest() { const tracker = new TestDataTracker() const kysely = getTestKysely() return { tracker, kysely } } export async function cleanupAfterTest( tracker: TestDataTracker, kysely: Kysely ) { // Clean up in reverse dependency order const orderedTables = [ "event_participants", "event_demographics_history", "newsletter_subscriptions", "campaign_tracking", "events", "profiles" ] for (const table of orderedTables) { const ids = tracker.getTables().get(table) if (ids && ids.size > 0) { await kysely .deleteFrom(table as any) .where("id", "in", Array.from(ids)) .execute() } } // Clean up auth users const supabase = getTestSupabaseClient() for (const email of tracker.getAuthEmails()) { try { const { data } = await supabase.auth.admin.listUsers() const user = data.users.find(u => u.email === email) if (user) { await supabase.auth.admin.deleteUser(user.id) } } catch (error) { console.error("Failed to delete auth user", email, error) } } } // Test data creation helpers export async function createTestProfile( tracker: TestDataTracker, kysely: Kysely, data: Partial ) { const profile = await kysely .insertInto("profiles") .values({ full_name: "Test User", email: `test-${Date.now()}@example.com`, ...data }) .returningAll() .executeTakeFirstOrThrow() tracker.track("profiles", profile.id) return profile } export async function createTestEvent( tracker: TestDataTracker, kysely: Kysely, data: Partial ) { const event = await kysely .insertInto("events") .values({ title: "Test Event", emoji: "🎉", event_status: "Finished", time_event_start: new Date().toISOString(), time_event_end: new Date().toISOString(), ...data }) .returningAll() .executeTakeFirstOrThrow() tracker.track("events", event.id) return event } ``` -------------------------------- ### Wait for Navigation and Click with TypeScript Source: https://github.com/angelod1as/positiv/blob/main/e2e/README.md Ensures navigation is complete and the network is idle before proceeding after a click action. This is crucial for reliable test execution. ```typescript await Promise.all([ page.waitForNavigation({ waitUntil: 'networkidle' }), submitButton.click() ]) ``` -------------------------------- ### Asaas API Client File Structure Setup Source: https://github.com/angelod1as/positiv/blob/main/docs/payment-system.md This outlines the recommended file structure for the Asaas API client integration within the `app/integrations/asaas/` directory. It includes dedicated files for API methods (`client.ts`), TypeScript types (`types.ts`), constants like URLs and timeouts (`constants.ts`), and unit tests (`client.test.ts`). This structure promotes modularity and maintainability. ```plaintext app/integrations/asaas/ ├── client.ts # API methods ├── types.ts # TypeScript types ├── constants.ts # URLs, timeouts └── client.test.ts # Unit tests ``` -------------------------------- ### Create and Switch to a New Git Worktree Source: https://github.com/angelod1as/positiv/blob/main/CLAUDE.md This snippet demonstrates how to create a new Git worktree for feature development, including copying environment variables and installing dependencies. It emphasizes using worktrees for clean workspace separation. ```bash # Create a new worktree for your feature git worktree add ../worktrees/feature-name feature/your-feature-name # Switch to the new worktree cd ../worktrees/feature-name # Copy environment variables from main worktree cp ../positiv/.env .env # Install dependencies pnpm install ``` -------------------------------- ### Build and Run E2E Tests (Bash) Source: https://github.com/angelod1as/positiv/blob/main/e2e/README.md Commands for executing E2E tests in the Positiv project. Supports running all tests, filtering by project (e.g., authenticated/unauthenticated), running specific files, and enabling headed or UI modes for debugging. Assumes Playwright configuration for project filtering. ```bash pnpm test:e2e pnpm test:e2e -- --project=chromium pnpm test:e2e -- --project=chromium-authenticated-user pnpm test:e2e -- --project=chromium-authenticated-admin pnpm test:e2e -- complete-journey pnpm test:e2e -- --headed pnpm test:e2e -- --ui ``` -------------------------------- ### Clear Test Data with Cleanup Utilities in TypeScript Source: https://github.com/angelod1as/positiv/blob/main/e2e/README.md Demonstrates the use of provided cleanup utilities to reset user states after test execution. This ensures a clean environment for subsequent tests. ```typescript await resetUserToDefaultState(testUser.email) ``` -------------------------------- ### Run Mailhog for Email Testing (Bash) Source: https://github.com/angelod1as/positiv/blob/main/README.md Starts Mailhog, a local SMTP testing tool, to capture and inspect emails sent during development. This is useful for testing email functionality without sending actual emails. ```bash pnpm email:test ``` -------------------------------- ### Unit Test Example with Vitest and React Testing Library Source: https://github.com/angelod1as/positiv/blob/main/CLAUDE.md Demonstrates a typical unit test structure using Vitest and React Testing Library for testing component interactions. It mocks event handlers and asserts their invocation after a user action. Dependencies include '@testing-library/react', '@testing-library/user-event', and 'vitest'. ```typescript import { render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { describe, expect, it, vi } from 'vitest' import { Component } from './component' describe('Component', () => { it('should handle user interaction', async () => { const user = userEvent.setup() const mockHandler = vi.fn() render() await user.click(screen.getByRole('button')) expect(mockHandler).toHaveBeenCalledTimes(1) }) }) ``` -------------------------------- ### Add Payment System Feature Flag to Environment Variables Source: https://github.com/angelod1as/positiv/blob/main/docs/payment-system.md This snippet demonstrates how to add a new environment variable `ENABLE_PAYMENT_SYSTEM` to control the payment system's feature flag. It includes schema definition, type augmentation, and an example for `.env.example`. The flag defaults to 'false' and is transformed to a boolean. ```typescript // Add to schema ENABLE_PAYMENT_SYSTEM: z .string() .default('false') .transform((val) => val === 'true'), // Add to type export type ENV = { // ... existing fields ENABLE_PAYMENT_SYSTEM: boolean; }; ``` ```bash # Payment System ENABLE_PAYMENT_SYSTEM=false # Set to 'true' to enable payment system ``` -------------------------------- ### Supabase Authentication and Context Management (TypeScript) Source: https://context7.com/angelod1as/positiv/llms.txt Manages server-side authentication using Supabase, including user retrieval, profile loading with roles via RPC, and context creation. It provides functions to get the Supabase client, the full authentication context, and requires authenticated user context for specific routes. Also includes auth guards for user and admin roles, and a login flow. ```typescript // app/business/auth/auth.server.ts import { createServerClient } from "~/lib/supabase/server" // Create Supabase client for server-side operations export async function getSupabase(request: Request, params: Params) { const { supabase, headers: supabaseHeaders } = createServerClient(request) return { supabaseHeaders, supabase } } // Get full authentication context with caching export async function getContext(request: Request, params: Params) { const { supabase, supabaseHeaders } = await getSupabase(request, params) const { data: authData, error } = await supabase.auth.getUser() if (error || !authData.user) { return { supabase, supabaseHeaders, currentUser: null, currentProfile: null, host: request.headers.get("host") } } // Load profile with roles using RPC function const { data: profileData } = await supabase .rpc("get_profile_with_roles", { user_id_input: authData.user.id }) .single() return { supabase, supabaseHeaders, currentUser: { id: authData.user.id, email: authData.user.email }, currentProfile: profileData, host: request.headers.get("host") } } // Require authenticated user context export async function getUserContext(request: Request, params: Params) { const context = await getContext(request, params) requireUser(context.currentUser) return context } // Usage in route loaders export async function loader({ request, params }: Route.LoaderArgs) { const context = await getUserContext(request, params) const { currentProfile, currentUser } = context // Now guaranteed to have authenticated user return { profile: currentProfile } } // Auth guards import { redirectWithError } from "remix-toast" export function requireUser(currentUser: CurrentUser) { if (!currentUser) { throw redirectWithError("/entrar", "Você precisa estar logade") } return currentUser } export function requireAdmin(currentProfile: CurrentProfile) { if (!currentProfile?.is_admin) { throw redirectWithError("/dashboard", "Sem permissão") } return currentProfile } // Login flow export const loginUser = applySchema(loginSchema)(async (values) => { const { supabase, supabaseHeaders } = await getSupabase(request, params) const { data, error } = await supabase.auth.signInWithPassword({ email: values.email, password: values.password, }) if (error) { return { success: false, error: "Credenciais inválidas" } } return redirect("/dashboard", { headers: combineHeaders(supabaseHeaders) }) }) ``` -------------------------------- ### Get Asaas Payment Status (TypeScript) Source: https://github.com/angelod1as/positiv/blob/main/docs/payment-system.md Retrieves the current status of a payment from Asaas using the provided payment ID. This function makes a GET request to the Asaas API and returns the payment details. Error handling is included for failed requests. ```typescript /** * Get payment status from Asaas */ export async function getPaymentStatus(paymentId: string): Promise { const { baseURL, headers } = getAsaasClient(); const response = await fetch(`${baseURL}/payments/${paymentId}`, { method: 'GET', headers, }); if (!response.ok) { const error = await response.json(); throw new Error(`Asaas get payment failed: ${JSON.stringify(error)}`); } return response.json(); } ``` -------------------------------- ### Run Vitest UI (Bash) Source: https://github.com/angelod1as/positiv/blob/main/README.md Launches the Vitest UI for interactive unit testing. This provides a visual interface for running, debugging, and managing unit tests. ```bash pnpm test:ui ``` -------------------------------- ### Run Integration Tests (Bash) Source: https://github.com/angelod1as/positiv/blob/main/README.md Executes integration tests that require a database connection. This command should be run after the local Supabase environment is set up and running. ```bash pnpm test:integration ``` -------------------------------- ### Handle Duplicate Object Creation in SQL Source: https://github.com/angelod1as/positiv/blob/main/CLAUDE.md Demonstrates how to gracefully handle potential duplicate object creation errors in SQL migrations using DO blocks with exception handling for types and IF NOT EXISTS for extensions. This prevents migration failures due to pre-existing objects. ```sql -- Use DO blocks with exception handling for types DO $$ BEGIN CREATE TYPE "public"."my_type" as ("field" varchar); EXCEPTION WHEN duplicate_object THEN null; END $$ ; -- Use IF NOT EXISTS for extensions CREATE EXTENSION IF NOT EXISTS pg_cron; ``` -------------------------------- ### Essential pnpm Commands for Development and Testing Source: https://github.com/angelod1as/positiv/blob/main/CLAUDE.md A collection of crucial pnpm commands for the project, covering development server startup, production builds, code quality checks (linting), and various testing scenarios including unit, integration, and E2E tests. ```bash # Development pnpm dev # Start development server (port 5173) # Build & Production pnpm build # Build for production pnpm start # Start production server # Code Quality - ALWAYS run before committing pnpm lint # Runs ESLint, generates types, and checks TypeScript # Testing pnpm test # Run unit tests and integration tests pnpm test:unit # Run unit tests only with Vitest pnpm test:integration # Run integration tests only (requires database) pnpm test:ui # Run unit tests with Vitest UI pnpm test:coverage # Run unit tests with coverage report pnpm test:watch # Run unit tests in watch mode pnpm test:e2e # Run Playwright E2E tests pnpm test:e2e:ui # Run E2E tests with UI ``` -------------------------------- ### Monitor Console Errors in Playwright with TypeScript Source: https://github.com/angelod1as/positiv/blob/main/e2e/README.md Sets up a listener for console messages, specifically capturing and logging any errors that occur during test execution. This helps in identifying runtime issues. ```typescript page.on('console', (msg) => { if (msg.type() === 'error') consoleErrors.push(msg.text()) }) ``` -------------------------------- ### TypeScript: Get Payment Amount for Participant Source: https://github.com/angelod1as/positiv/blob/main/docs/payment-system.md TypeScript function to retrieve the payment amount for an event participant. Returns 0 for non-regular spots and the amount from the associated payment transaction for regular spots. ```typescript function getPaymentAmount(participant: EventParticipant): number { if (participant.spot_type !== 'regular') return 0; return participant.payment_transaction?.amount ?? 0; } ``` -------------------------------- ### Run E2E Tests with pnpm Source: https://github.com/angelod1as/positiv/blob/main/CLAUDE.md These commands show how to execute end-to-end tests using the pnpm package manager. You can run all tests, the UI mode for debugging, or specific test suites based on authentication context and browser projects. ```bash pnpm test:e2e # Run all E2E tests pnpm test:e2e:ui # Run with Playwright UI for debugging # Run specific test suites pnpm test:e2e -- --project=chromium # Unauthenticated tests pnpm test:e2e -- --project=chromium-authenticated-user # User tests pnpm test:e2e -- --project=chromium-authenticated-admin # Admin tests ``` -------------------------------- ### Leverage Auth State for Playwright Tests in TypeScript Source: https://github.com/angelod1as/positiv/blob/main/e2e/README.md Utilizes Playwright's 'storageState' option to persist authentication tokens, avoiding the need for repeated login sequences. This significantly speeds up test execution. ```typescript test.use({ storageState: 'e2e/.auth/user.json' }) ``` -------------------------------- ### Clone Positiv Repository (Bash) Source: https://github.com/angelod1as/positiv/blob/main/README.md Clones the Positiv project repository from GitHub and navigates into the project directory. This is the first step in setting up the development environment. ```bash git clone https://github.com/angelod1as/positiv.git cd positiv ``` -------------------------------- ### Optimized Index for Auto-Publish Query Source: https://github.com/angelod1as/positiv/blob/main/docs/newsletter-event-automation.md SQL statement for creating a performance-optimized index on the 'events' table. This index specifically targets queries filtering by 'Scheduled' status and 'auto_publish' enabled, along with registration start time. ```sql CREATE INDEX idx_events_auto_publish_status ON events(event_status, auto_publish, time_application_start) WHERE event_status = 'Scheduled' AND auto_publish = true; ``` -------------------------------- ### TypeScript: Get Payment Status Badge Source: https://github.com/angelod1as/positiv/blob/main/docs/payment-system.md TypeScript function to determine the payment status badge for an event participant. It returns 'free', 'pending', or 'paid' based on the participant's spot type and payment transaction status. ```typescript function getPaymentStatus(participant: EventParticipant): 'free' | 'pending' | 'paid' { if (participant.spot_type !== 'regular') return 'free'; return participant.payment_transaction_id ? 'paid' : 'pending'; } ``` -------------------------------- ### Flexible URL Assertion with Regex in TypeScript Source: https://github.com/angelod1as/positiv/blob/main/e2e/README.md Uses regular expressions to assert URLs, allowing for flexibility with query parameters or dynamic URL segments. This prevents test failures due to minor URL variations. ```typescript await expect(page).toHaveURL(//entrar/) ``` -------------------------------- ### Check for Object Existence Before Dropping in SQL Source: https://github.com/angelod1as/positiv/blob/main/CLAUDE.md Shows how to safely drop SQL objects like extensions or types by first checking if they exist using `IF EXISTS`. This prevents errors during migration or cleanup scripts if the objects are already gone. ```sql DROP EXTENSION IF EXISTS pg_net; DROP TYPE IF EXISTS "public"."my_type"; ``` -------------------------------- ### Fetch Events for Admin Dashboard (TypeScript) Source: https://context7.com/angelod1as/positiv/llms.txt Retrieves a list of recent events for the admin dashboard. It selects key event details and orders them by start time in descending order, limiting the results to 50. This function relies on the 'kysely' database client. ```typescript import { kysely } from "~/kysely" import { sql } from "kysely" // Get all events for admin dashboard export async function getEventsForDashboard() { return await kysely .selectFrom("events") .select([ "id", "title", "emoji", "event_status", "location", "ticket_price", "time_event_start", "time_application_end", ]) .orderBy("time_event_start", "desc") .limit(50) .execute() } ``` -------------------------------- ### Get or Create Asaas Customer (TypeScript) Source: https://github.com/angelod1as/positiv/blob/main/docs/payment-system.md Retrieves an existing Asaas customer by email or creates a new one if it doesn't exist. The profile's email is used as the primary identifier for searching customers. This function handles both lookup and creation of customer records in Asaas. ```typescript /** * Get or create Asaas customer for a profile * For now, we'll use profile email as customer identifier */ export async function getOrCreateAsaasCustomer(profile: { id: string; email: string; name: string; cpf?: string; }): Promise { const { baseURL, headers } = getAsaasClient(); // Try to find existing customer by email const searchResponse = await fetch( `${baseURL}/customers?email=${encodeURIComponent(profile.email)}`, { method: 'GET', headers } ); if (searchResponse.ok) { const customers = await searchResponse.json(); if (customers.data && customers.data.length > 0) { return customers.data[0].id; } } // Create new customer const createResponse = await fetch(`${baseURL}/customers`, { method: 'POST', headers, body: JSON.stringify({ name: profile.name, email: profile.email, cpfCnpj: profile.cpf, externalReference: profile.id, }), }); if (!createResponse.ok) { const error = await createResponse.json(); throw new Error(`Asaas create customer failed: ${JSON.stringify(error)}`); } const customer = await createResponse.json(); return customer.id; } ``` -------------------------------- ### Generate Payment Link and WhatsApp Integration (TypeScript) Source: https://github.com/angelod1as/positiv/blob/main/docs/payment-system.md Implements a 'Generate Payment Link' button for the participant table, using React Router's `useFetcher` for server interactions. It conditionally displays the button for regular, unpaid spots, generates a payment link upon click, copies it to the clipboard, and opens WhatsApp with a pre-filled message. Includes error handling and toast notifications. ```typescript import { CreditCardIcon } from 'lucide-react'; import { useFetcher } from 'react-router'; import { toast } from 'sonner'; import { phoneToWhatsAppLink } from '~/lib/helpers/phone-to-whatsapp-link'; // Inside component const paymentFetcher = useFetcher(); // Add button to buttons array const buttons = [ // ... existing buttons { Icon: CreditCardIcon, title: 'Gerar link de pagamento', key: 'id' as const, onClick: async (row: ParticipantRow) => { // Only show for regular spots without payment if (row.spot_type !== 'regular' || row.payment_transaction_id) { return; } paymentFetcher.submit( { intent: 'generate-payment-link', id: row.id, }, { method: 'post' } ); }, }, ]; // Handle fetcher response useEffect(() => { if (paymentFetcher.data?.success) { const { paymentLink, whatsappMessage } = paymentFetcher.data; // Copy link to clipboard navigator.clipboard.writeText(paymentLink); toast.success('Link copiado para área de transferência!'); // Open WhatsApp with pre-filled message const phone = /* get participant phone */; if (phone) { const whatsappUrl = phoneToWhatsAppLink(phone, whatsappMessage); window.open(whatsappUrl, '_blank'); } } if (paymentFetcher.data?.error) { toast.error(`Erro ao gerar link: ${paymentFetcher.data.error}`); } }, [paymentFetcher.data]); // Conditionally render button based on payment status const shouldShowPaymentButton = (row: ParticipantRow) => { return row.spot_type === 'regular' && !row.payment_transaction_id; }; ```