# Gebeya WhatsApp OTP

A React component library for WhatsApp-based OTP verification with Supabase integration. This package provides a complete solution for phone number authentication using WhatsApp OTP, including a provider pattern, modal interface, and comprehensive error handling.

## Features

- 🔐 **WhatsApp OTP Verification**: Send and verify OTP codes via WhatsApp
- ⚛️ **React Context Provider**: Easy state management with React Context
- 🎨 **Pre-built UI Components**: Ready-to-use modal and button components
- 📱 **Country Selection**: Built-in country code selector with flags
- 🔄 **Retry Logic**: Automatic attempt tracking and suspension handling
- 🎯 **TypeScript Support**: Full TypeScript definitions included
- 🎨 **Tailwind CSS**: Styled with Tailwind CSS classes
- 🔧 **Customizable**: Flexible configuration and callback system

## Installation

```bash
npm install @benny_gebeya/gebeya-whatsapp-otp
```

### Peer Dependencies

This package requires React 16.8.0 or higher:

```bash
npm install react react-dom
```

## Quick Start

### 1. Wrap your app with the provider

```tsx
import React from "react";
import { WhatsAppOTPProvider } from "@benny_gebeya/gebeya-whatsapp-otp";

const config = {
  supabaseUrl: "https://your-project.supabase.co",
  supabaseKey: "your-anon-key",
  defaultCountry: "+251", // Optional: Default to Ethiopia
};

const callbacks = {
  onSuccess: (phoneNumber: string) => {
    console.log("Phone verified:", phoneNumber);
  },
  onError: (error: string) => {
    console.error("Verification error:", error);
  },
};

function App() {
  return (
    <WhatsAppOTPProvider config={config} callbacks={callbacks}>
      <YourAppContent />
    </WhatsAppOTPProvider>
  );
}
```

### 2. Add the verification components

```tsx
import React from "react";
import {
  VerifyWithWhatsAppButton,
  WhatsAppOTPModal,
} from "@benny_gebeya/gebeya-whatsapp-otp";

function YourAppContent() {
  return (
    <div>
      <h1>Welcome to our app</h1>
      <VerifyWithWhatsAppButton />
      <WhatsAppOTPModal />
    </div>
  );
}
```

## API Reference

### WhatsAppOTPProvider

The main provider component that manages OTP verification state.

#### Props

| Prop        | Type                   | Required | Description            |
| ----------- | ---------------------- | -------- | ---------------------- |
| `children`  | `React.ReactNode`      | ✅       | Child components       |
| `config`    | `WhatsAppOTPConfig`    | ✅       | Supabase configuration |
| `callbacks` | `WhatsAppOTPCallbacks` | ❌       | Event callbacks        |

#### WhatsAppOTPConfig

```typescript
interface WhatsAppOTPConfig {
  /** Supabase project URL */
  supabaseUrl: string;
  /** Supabase anon/service role key */
  supabaseKey: string;
  /** Default country code (e.g., "+251" for Ethiopia) */
  defaultCountry?: string;
}
```

#### WhatsAppOTPCallbacks

```typescript
interface WhatsAppOTPCallbacks {
  /** Called when OTP verification is successful */
  onSuccess?: (phoneNumber: string) => void;
  /** Called when an error occurs during the process */
  onError?: (error: string) => void;
  /** Called when the verification step changes */
  onStepChange?: (step: VerificationStep) => void;
  /** Called when a phone number gets suspended */
  onSuspension?: (message: string) => void;
}
```

### VerifyWithWhatsAppButton

A button component that opens the verification modal.

#### Props

| Prop        | Type                                                                          | Default                  | Description                |
| ----------- | ----------------------------------------------------------------------------- | ------------------------ | -------------------------- |
| `children`  | `React.ReactNode`                                                             | `"Verify with WhatsApp"` | Button text                |
| `className` | `string`                                                                      | `""`                     | Additional CSS classes     |
| `variant`   | `"default" \| "destructive" \| "outline" \| "secondary" \| "ghost" \| "link"` | `"default"`              | Button style variant       |
| `size`      | `"default" \| "sm" \| "lg" \| "icon"`                                         | `"default"`              | Button size                |
| `disabled`  | `boolean`                                                                     | `false`                  | Whether button is disabled |

#### Example

```tsx
<VerifyWithWhatsAppButton
  variant="outline"
  size="lg"
  className="my-custom-class"
>
  Start Verification
</VerifyWithWhatsAppButton>
```

### WhatsAppOTPModal

The modal component that handles the verification flow.

#### Props

| Prop        | Type              | Default      | Description            |
| ----------- | ----------------- | ------------ | ---------------------- |
| `countries` | `CountryOption[]` | Default list | Custom country options |

#### CountryOption

```typescript
interface CountryOption {
  /** Country calling code (e.g., "+251") */
  code: string;
  /** Country name (e.g., "Ethiopia") */
  country: string;
  /** Country flag emoji (e.g., "🇪🇹") */
  flag: string;
}
```

#### Example with custom countries

```tsx
const customCountries = [
  { code: "+1", country: "United States", flag: "🇺🇸" },
  { code: "+44", country: "United Kingdom", flag: "🇬🇧" },
  { code: "+251", country: "Ethiopia", flag: "🇪🇹" },
];

<WhatsAppOTPModal countries={customCountries} />;
```

### useWhatsAppOTP Hook

Access the OTP verification state and actions directly.

```typescript
import { useWhatsAppOTPContext } from "@benny_gebeya/gebeya-whatsapp-otp";

function CustomComponent() {
  const { step, phoneNumber, isLoading, sendOTP, verifyOTP, openModal } =
    useWhatsAppOTPContext();

  return (
    <div>
      <p>Current step: {step}</p>
      <button onClick={openModal}>Open Verification</button>
    </div>
  );
}
```

#### Hook Return Value

```typescript
interface WhatsAppOTPContextType {
  // State properties
  step: VerificationStep;
  countryCode: string;
  phoneNumber: string;
  otpCode: string;
  isLoading: boolean;
  attemptsLeft: number;
  maxAttemptsReached: boolean;
  isSuspended: boolean;
  suspensionMessage: string;
  isModalOpen: boolean;

  // Action functions
  setCountryCode: (code: string) => void;
  setPhoneNumber: (number: string) => void;
  setOtpCode: (code: string) => void;
  sendOTP: () => Promise<void>;
  verifyOTP: () => Promise<void>;
  resetFlow: () => void;
  openModal: () => void;
  closeModal: () => void;
}
```

## TypeScript Usage

The package includes full TypeScript support. Import types as needed:

```typescript
import {
  WhatsAppOTPConfig,
  WhatsAppOTPCallbacks,
  VerificationStep,
  CountryOption,
  WhatsAppOTPContextType,
} from "@benny_gebeya/gebeya-whatsapp-otp";

// Example: Type-safe configuration
const config: WhatsAppOTPConfig = {
  supabaseUrl: process.env.REACT_APP_SUPABASE_URL!,
  supabaseKey: process.env.REACT_APP_SUPABASE_ANON_KEY!,
  defaultCountry: "+251",
};

// Example: Type-safe callbacks
const callbacks: WhatsAppOTPCallbacks = {
  onSuccess: (phoneNumber: string) => {
    // Handle successful verification
    console.log(`Verified: ${phoneNumber}`);
  },
  onError: (error: string) => {
    // Handle errors
    console.error("Verification failed:", error);
  },
  onStepChange: (step: VerificationStep) => {
    // Track verification progress
    console.log(`Step changed to: ${step}`);
  },
};
```

## Advanced Examples

### Custom Error Handling

```tsx
import React, { useState } from "react";
import {
  WhatsAppOTPProvider,
  VerifyWithWhatsAppButton,
  WhatsAppOTPModal,
} from "@benny_gebeya/gebeya-whatsapp-otp";

function App() {
  const [error, setError] = useState<string>("");
  const [isVerified, setIsVerified] = useState(false);

  const config = {
    supabaseUrl: "https://your-project.supabase.co",
    supabaseKey: "your-anon-key",
  };

  const callbacks = {
    onSuccess: (phoneNumber: string) => {
      setIsVerified(true);
      setError("");
      // Redirect or update UI
    },
    onError: (error: string) => {
      setError(error);
    },
    onSuspension: (message: string) => {
      setError(`Account suspended: ${message}`);
    },
  };

  return (
    <WhatsAppOTPProvider config={config} callbacks={callbacks}>
      <div className="p-4">
        {error && (
          <div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
            {error}
          </div>
        )}

        {isVerified ? (
          <div className="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded">
            Phone number verified successfully!
          </div>
        ) : (
          <VerifyWithWhatsAppButton />
        )}

        <WhatsAppOTPModal />
      </div>
    </WhatsAppOTPProvider>
  );
}
```

### Integration with Form Libraries

```tsx
import React from "react";
import { useForm } from "react-hook-form";
import {
  WhatsAppOTPProvider,
  useWhatsAppOTPContext,
} from "@benny_gebeya/gebeya-whatsapp-otp";

interface FormData {
  name: string;
  email: string;
  phoneVerified: boolean;
}

function RegistrationForm() {
  const { register, handleSubmit, setValue, watch } = useForm<FormData>();
  const { step } = useWhatsAppOTPContext();

  const phoneVerified = watch("phoneVerified");

  const onSubmit = (data: FormData) => {
    if (!data.phoneVerified) {
      alert("Please verify your phone number first");
      return;
    }
    // Submit form
    console.log("Form submitted:", data);
  };

  const callbacks = {
    onSuccess: () => {
      setValue("phoneVerified", true);
    },
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
      <input
        {...register("name", { required: true })}
        placeholder="Full Name"
        className="w-full p-2 border rounded"
      />

      <input
        {...register("email", { required: true })}
        type="email"
        placeholder="Email"
        className="w-full p-2 border rounded"
      />

      <div className="flex items-center gap-2">
        <VerifyWithWhatsAppButton size="sm" />
        {phoneVerified && <span className="text-green-600">✓ Verified</span>}
      </div>

      <button
        type="submit"
        disabled={!phoneVerified}
        className="w-full bg-blue-600 text-white p-2 rounded disabled:opacity-50"
      >
        Register
      </button>

      <WhatsAppOTPModal />
    </form>
  );
}
```

## Styling

The components use Tailwind CSS classes and are designed to work with your existing Tailwind setup. The components include:

- Responsive design
- Dark mode support
- Accessible focus states
- Consistent spacing and typography

### Custom Styling

You can override styles by passing custom `className` props:

```tsx
<VerifyWithWhatsAppButton className="bg-purple-600 hover:bg-purple-700 text-white font-bold py-2 px-4 rounded-full">
  Custom Styled Button
</VerifyWithWhatsAppButton>
```

## Requirements

- React 16.8.0 or higher
- Supabase project with WhatsApp OTP function deployed
- Tailwind CSS (for styling)
- WhatsApp Business API access (Facebook/Meta)

## Supabase Setup

This package requires a Supabase edge function and database tables to handle WhatsApp OTP functionality. Follow these steps to set up your Supabase project:

### 1. Create Database Tables

Run these SQL commands in your Supabase SQL editor:

#### Complete Database Schema

Run this complete SQL script in your Supabase SQL editor to set up all required tables, indexes, and security policies:

```sql
-- Set up database configuration
SET statement_timeout = 0;
SET lock_timeout = 0;
SET idle_in_transaction_session_timeout = 0;
SET client_encoding = 'UTF8';
SET standard_conforming_strings = on;
SELECT pg_catalog.set_config('search_path', '', false);
SET check_function_bodies = false;
SET xmloption = content;
SET client_min_messages = warning;
SET row_security = off;

-- Create extensions if they don't exist
CREATE EXTENSION IF NOT EXISTS "pg_graphql" WITH SCHEMA "graphql";
CREATE EXTENSION IF NOT EXISTS "pg_stat_statements" WITH SCHEMA "extensions";
CREATE EXTENSION IF NOT EXISTS "pgcrypto" WITH SCHEMA "extensions";
CREATE EXTENSION IF NOT EXISTS "supabase_vault" WITH SCHEMA "vault";
CREATE EXTENSION IF NOT EXISTS "uuid-ossp" WITH SCHEMA "extensions";

-- Create utility function for updating timestamps
CREATE OR REPLACE FUNCTION "public"."update_updated_at_column"()
RETURNS "trigger"
LANGUAGE "plpgsql"
SET "search_path" TO ''
AS $$
BEGIN
  NEW.updated_at = now();
  RETURN NEW;
END;
$$;

-- Create OTP codes table
CREATE TABLE IF NOT EXISTS "public"."otp_codes" (
  "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL,
  "phone_number" "text" NOT NULL,
  "code" "text" NOT NULL,
  "expires_at" timestamp with time zone NOT NULL,
  "used" boolean DEFAULT false NOT NULL,
  "created_at" timestamp with time zone DEFAULT "now"() NOT NULL,
  "attempts_left" integer DEFAULT 3 NOT NULL,
  "suspended_until" timestamp with time zone,
  "suspension_reason" "text"
);

-- Add table comment
COMMENT ON COLUMN "public"."otp_codes"."expires_at" IS 'OTP codes should expire within 2-3 minutes for security';

-- Create primary key
ALTER TABLE ONLY "public"."otp_codes"
ADD CONSTRAINT "otp_codes_pkey" PRIMARY KEY ("id");

-- Create indexes for better performance
CREATE INDEX "idx_otp_codes_expires_at" ON "public"."otp_codes" USING "btree" ("expires_at");
CREATE INDEX "idx_otp_codes_phone_number" ON "public"."otp_codes" USING "btree" ("phone_number");
CREATE INDEX "idx_otp_codes_phone_suspended" ON "public"."otp_codes" USING "btree" ("phone_number", "suspended_until");

-- Create Row Level Security policies
CREATE POLICY "Anyone can insert OTP codes" ON "public"."otp_codes" FOR INSERT WITH CHECK (true);
CREATE POLICY "Anyone can select OTP codes" ON "public"."otp_codes" FOR SELECT USING (true);
CREATE POLICY "Anyone can update OTP codes" ON "public"."otp_codes" FOR UPDATE USING (true);

-- Enable Row Level Security
ALTER TABLE "public"."otp_codes" ENABLE ROW LEVEL SECURITY;

-- Grant necessary permissions
GRANT ALL ON TABLE "public"."otp_codes" TO "anon";
GRANT ALL ON TABLE "public"."otp_codes" TO "authenticated";
GRANT ALL ON TABLE "public"."otp_codes" TO "service_role";
GRANT ALL ON FUNCTION "public"."update_updated_at_column"() TO "anon";
GRANT ALL ON FUNCTION "public"."update_updated_at_column"() TO "authenticated";
GRANT ALL ON FUNCTION "public"."update_updated_at_column"() TO "service_role";
```

### 2. Deploy the Edge Function

#### Step 1: Install Supabase CLI

```bash
npm install -g supabase
```

#### Step 2: Login to Supabase

```bash
supabase login
```

#### Step 3: Initialize Supabase in your project (if not already done)

```bash
supabase init
```

#### Step 4: Create the edge function

```bash
supabase functions new otp_whatsapp
```

#### Step 5: Replace the function code

Replace the contents of `supabase/functions/otp_whatsapp/index.ts` with:

```typescript
import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";

const corsHeaders = {
  "Access-Control-Allow-Origin": "*",
  "Access-Control-Allow-Headers":
    "authorization, x-client-info, apikey, content-type",
};

serve(async (req) => {
  // Handle CORS preflight requests
  if (req.method === "OPTIONS") {
    return new Response(null, { headers: corsHeaders });
  }

  try {
    const supabase = createClient(
      Deno.env.get("SUPABASE_URL") ?? "",
      Deno.env.get("SUPABASE_ANON_KEY") ?? ""
    );

    // Create admin client for user creation
    const supabaseAdmin = createClient(
      Deno.env.get("SUPABASE_URL") ?? "",
      Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") ?? ""
    );

    const { action, phone_number, code } = await req.json();

    if (action === "send_otp") {
      // Check if phone number is currently suspended
      const { data: suspensionCheck } = await supabase
        .from("otp_codes")
        .select("suspended_until, suspension_reason")
        .eq("phone_number", phone_number)
        .not("suspended_until", "is", null)
        .gt("suspended_until", new Date().toISOString())
        .order("suspended_until", { ascending: false })
        .limit(1);

      if (suspensionCheck && suspensionCheck.length > 0) {
        const suspension = suspensionCheck[0];
        const remainingTime = Math.ceil(
          (new Date(suspension.suspended_until).getTime() -
            new Date().getTime()) /
            1000
        );
        const minutes = Math.ceil(remainingTime / 60);

        return new Response(
          JSON.stringify({
            error: `Phone number is temporarily suspended. Please try again in ${minutes} minute${
              minutes !== 1 ? "s" : ""
            }.`,
          }),
          {
            status: 429,
            headers: { ...corsHeaders, "Content-Type": "application/json" },
          }
        );
      }

      // Generate 6-digit OTP
      const otpCode = Math.floor(100000 + Math.random() * 900000).toString();
      const expiresAt = new Date();
      expiresAt.setMinutes(expiresAt.getMinutes() + 2); // 2 minutes expiry

      console.log(`Generating OTP for phone ${phone_number}: ${otpCode}`);

      // Store OTP in database
      const { error: otpError } = await supabase.from("otp_codes").insert({
        phone_number,
        code: otpCode,
        expires_at: expiresAt.toISOString(),
        suspended_until: null,
        suspension_reason: null,
      });

      if (otpError) {
        console.error("Error storing OTP:", otpError);
        return new Response(
          JSON.stringify({ error: "Failed to generate OTP" }),
          {
            status: 500,
            headers: { ...corsHeaders, "Content-Type": "application/json" },
          }
        );
      }

      // Send WhatsApp message using Facebook Graph API
      const facebookToken = Deno.env.get("FACEBOOK_ACCESS_TOKEN");
      const phoneNumberId = Deno.env.get("WHATSAPP_PHONE_NUMBER_ID");

      const whatsappPayload = {
        messaging_product: "whatsapp",
        to: phone_number,
        type: "template",
        template: {
          name: "otp_verification_code",
          language: { code: "en_US" },
          components: [
            {
              type: "BODY",
              parameters: [{ type: "text", text: otpCode }],
            },
            {
              type: "BUTTON",
              sub_type: "URL",
              index: "0",
              parameters: [{ type: "text", text: otpCode }],
            },
          ],
        },
      };

      const whatsappResponse = await fetch(
        `https://graph.facebook.com/v22.0/${phoneNumberId}/messages`,
        {
          method: "POST",
          headers: {
            Authorization: `Bearer ${facebookToken}`,
            "Content-Type": "application/json",
          },
          body: JSON.stringify(whatsappPayload),
        }
      );

      const whatsappResult = await whatsappResponse.json();

      if (!whatsappResponse.ok) {
        console.error("WhatsApp API error:", whatsappResult);
        return new Response(
          JSON.stringify({
            error: "Failed to send WhatsApp message",
            details: whatsappResult,
          }),
          {
            status: 500,
            headers: { ...corsHeaders, "Content-Type": "application/json" },
          }
        );
      }

      return new Response(
        JSON.stringify({
          success: true,
          message: "OTP sent successfully",
          message_id: whatsappResult.messages?.[0]?.id,
        }),
        {
          headers: { ...corsHeaders, "Content-Type": "application/json" },
        }
      );
    } else if (action === "verify_otp") {
      // Check if phone number is currently suspended
      const { data: suspensionCheck } = await supabase
        .from("otp_codes")
        .select("suspended_until, suspension_reason")
        .eq("phone_number", phone_number)
        .not("suspended_until", "is", null)
        .gt("suspended_until", new Date().toISOString())
        .order("suspended_until", { ascending: false })
        .limit(1);

      if (suspensionCheck && suspensionCheck.length > 0) {
        const suspension = suspensionCheck[0];
        const remainingTime = Math.ceil(
          (new Date(suspension.suspended_until).getTime() -
            new Date().getTime()) /
            1000
        );
        const minutes = Math.ceil(remainingTime / 60);

        return new Response(
          JSON.stringify({
            error: `Phone number is temporarily suspended. Please try again in ${minutes} minute${
              minutes !== 1 ? "s" : ""
            }.`,
          }),
          {
            status: 429,
            headers: { ...corsHeaders, "Content-Type": "application/json" },
          }
        );
      }

      // Check if there's a valid OTP for this phone number
      const { data: otpRecord, error: fetchError } = await supabase
        .from("otp_codes")
        .select("*")
        .eq("phone_number", phone_number)
        .eq("used", false)
        .gt("expires_at", new Date().toISOString())
        .order("created_at", { ascending: false })
        .limit(1)
        .maybeSingle();

      if (fetchError) {
        console.error("Error fetching OTP:", fetchError);
        return new Response(JSON.stringify({ error: "Verification failed" }), {
          status: 500,
          headers: { ...corsHeaders, "Content-Type": "application/json" },
        });
      }

      if (!otpRecord) {
        return new Response(
          JSON.stringify({ error: "Invalid or expired OTP code" }),
          {
            status: 400,
            headers: { ...corsHeaders, "Content-Type": "application/json" },
          }
        );
      }

      // Check if the code matches
      if (otpRecord.code !== code) {
        const newAttemptsLeft = otpRecord.attempts_left - 1;

        await supabase
          .from("otp_codes")
          .update({ attempts_left: newAttemptsLeft })
          .eq("id", otpRecord.id);

        if (newAttemptsLeft <= 0) {
          // Suspend the phone number for 3 minutes
          const suspendedUntil = new Date();
          suspendedUntil.setMinutes(suspendedUntil.getMinutes() + 3);

          await supabase
            .from("otp_codes")
            .update({
              used: true,
              suspended_until: suspendedUntil.toISOString(),
              suspension_reason: "max_attempts_reached",
            })
            .eq("id", otpRecord.id);

          return new Response(
            JSON.stringify({
              error:
                "Maximum attempts reached. Your phone number has been temporarily suspended for 3 minutes.",
            }),
            {
              status: 429,
              headers: { ...corsHeaders, "Content-Type": "application/json" },
            }
          );
        }

        return new Response(
          JSON.stringify({
            error: `Invalid OTP code. ${newAttemptsLeft} attempts remaining.`,
          }),
          {
            status: 400,
            headers: { ...corsHeaders, "Content-Type": "application/json" },
          }
        );
      }

      // Mark OTP as used
      const { error: updateError } = await supabase
        .from("otp_codes")
        .update({ used: true })
        .eq("id", otpRecord.id);

      if (updateError) {
        console.error("Error updating OTP:", updateError);
        return new Response(JSON.stringify({ error: "Verification failed" }), {
          status: 500,
          headers: { ...corsHeaders, "Content-Type": "application/json" },
        });
      }

      // Handle user authentication
      const { data: existingUsers } =
        await supabaseAdmin.auth.admin.listUsers();
      const existingUser = existingUsers.users?.find(
        (u) => u.phone === phone_number
      );

      let user;
      if (existingUser) {
        user = existingUser;
      } else {
        // Create new user with phone number
        const { data: newUserData, error: createError } =
          await supabaseAdmin.auth.admin.createUser({
            phone: phone_number,
            phone_confirm: true,
            email_confirm: true,
            user_metadata: {
              phone: phone_number,
              provider: "phone",
            },
          });

        if (createError) {
          console.error("Error creating user:", createError);
          return new Response(
            JSON.stringify({ error: "Failed to create user account" }),
            {
              status: 500,
              headers: { ...corsHeaders, "Content-Type": "application/json" },
            }
          );
        }

        user = newUserData.user;
      }

      // Send success template
      const facebookToken = Deno.env.get("FACEBOOK_ACCESS_TOKEN");
      const phoneNumberId = Deno.env.get("WHATSAPP_PHONE_NUMBER_ID");

      const successPayload = {
        messaging_product: "whatsapp",
        to: phone_number,
        type: "template",
        template: {
          name: "otp_success",
          language: { code: "en_US" },
        },
      };

      await fetch(
        `https://graph.facebook.com/v22.0/${phoneNumberId}/messages`,
        {
          method: "POST",
          headers: {
            Authorization: `Bearer ${facebookToken}`,
            "Content-Type": "application/json",
          },
          body: JSON.stringify(successPayload),
        }
      );

      return new Response(
        JSON.stringify({
          success: true,
          message: "Phone number verified and user authenticated successfully",
          verified: true,
          user: user,
        }),
        {
          headers: { ...corsHeaders, "Content-Type": "application/json" },
        }
      );
    }

    return new Response(JSON.stringify({ error: "Invalid action" }), {
      status: 400,
      headers: { ...corsHeaders, "Content-Type": "application/json" },
    });
  } catch (error) {
    console.error("Error in otp_whatsapp function:", error);
    return new Response(JSON.stringify({ error: error.message }), {
      status: 500,
      headers: { ...corsHeaders, "Content-Type": "application/json" },
    });
  }
});
```

#### Step 6: Set up environment variables

```bash
# Set your environment variables
supabase secrets set FACEBOOK_ACCESS_TOKEN=your_facebook_access_token
```

#### Step 7: Deploy the function

```bash
supabase functions deploy otp_whatsapp
```

### 3. WhatsApp Business API Setup

1. **Create a Meta Business Account** at https://business.facebook.com
2. **Set up WhatsApp Business API** through Meta Business
3. **Create message templates** in your WhatsApp Business Manager:
   - Template name: `otp_verification_code`
   - Template name: `otp_success`
4. **Get your access token** and phone number ID from the Meta Developer Console
5. **Configure webhooks** (optional) for delivery status

### 4. Update Your App Configuration

Make sure your app configuration points to your Supabase project:

```tsx
const config = {
  supabaseUrl: "https://your-project.supabase.co",
  supabaseKey: "your-anon-key", // This should be your anon/public key
};
```

### 5. Test Your Setup

You can test your edge function directly:

```bash
curl -X POST 'https://your-project.supabase.co/functions/v1/otp_whatsapp' \
  -H 'Authorization: Bearer YOUR_ANON_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "action": "send_otp",
    "phone_number": "+1234567890"
  }'
```

## License

MIT

## Support

For issues and questions, please visit our [GitHub repository](https://github.com/gebeya/gebeya-whatsapp-otp).

## Development

### Package Validation

This package includes a comprehensive validation script to ensure the build is correct and ready for publishing. The validation script tests:

- Build file existence and formats
- Package.json configuration
- Import functionality in both JavaScript and TypeScript
- Peer dependency resolution
- Basic component functionality

#### Running Validation

```bash
# Run the full package validation
npm run validate

# Run just the build validation
npm run validate-build
```

#### What Gets Validated

1. **Build Files**: Checks that all required build outputs exist

   - `dist/index.cjs` (CommonJS build)
   - `dist/index.esm.js` (ES modules build)
   - `dist/index.d.ts` (TypeScript declarations)
   - Component-specific declaration files

2. **Package Configuration**: Validates package.json settings

   - Entry points (main, module, types)
   - Peer dependencies configuration
   - File inclusion settings

3. **Import Testing**: Tests imports in different environments

   - CommonJS require() syntax
   - ES modules import syntax
   - TypeScript type checking and compilation

4. **Peer Dependencies**: Verifies React is properly externalized

   - Checks that React is not bundled in the output
   - Validates peer dependency resolution

5. **Component Functionality**: Basic validation of exported components
   - Ensures all exports are available
   - Validates component types and structure

#### Validation Output

The validation script provides detailed feedback:

```
🔍 Comprehensive Package Validation for gebeya-whatsapp-otp

1. Checking build output files...
   ✓ dist/index.cjs exists
   ✓ dist/index.esm.js exists
   ✓ dist/index.d.ts exists
   ...

✅ Package validation PASSED
📦 Package is ready for publishing
```

### Building the Package

```bash
# Build the package
npm run build

# Build and validate
npm run build && npm run validate
```

The build process:

1. Compiles TypeScript to generate declaration files
2. Bundles with Rollup to create CommonJS and ES module outputs
3. Generates source maps for debugging
4. Excludes peer dependencies from the bundle
