Update user phone no in superbase if user login with email and password

Hi, I am able to log in and sign up users with email and password, but I also want to include phone verification, which I haven’t been able to implement successfully. I’ve tried two approaches, but both failed.

Approach 1:
Here I use superbase Auth update approach which is failed to fetch user
Code component otp verifications:

import { useState } from 'react';
import { createClient } from '@supabase/supabase-js';

const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL,
  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
);

export function OtpVerificationComponent({ userEmail, userId }) {
  const [phoneNumber, setPhoneNumber] = useState('');
  const [otpSent, setOtpSent] = useState(false);
  const [otp, setOtp] = useState('');
  const [error, setError] = useState('');
  const [success, setSuccess] = useState('');

  const sendOtp = async () => {
    if (!userEmail || !userId) {
      setError('No logged-in user. Please log in first.');
      return;
    }

    try {
      const { error } = await supabase.auth.updateUser({
        phone: phoneNumber,
      });

      if (error) {
        setError(`Failed to send OTP: ${error.message}`);
      } else {
        setOtpSent(true);
        setError('');
      }
    } catch (error) {
      setError(`An error occurred while sending OTP: ${error.message}`);
    }
  };

  const verifyOtp = async () => {
    if (!userEmail || !userId) {
      setError('No logged-in user. Please log in first.');
      return;
    }

    try {
      const { data, error } = await supabase.auth.verifyOtp({
        phone: phoneNumber,
        token: otp,
        type: 'phone_change',
      });

      if (error) {
        setError(`Invalid OTP: ${error.message}`);
      } else {
        setSuccess('Phone number verified and updated successfully.');
        setError('');
      }
    } catch (error) {
      setError('An error occurred while verifying OTP: ' + error.message);
    }
  };

  return (
    <div
      style={{
        width: '100%',
        height: '100%',
        position: 'fixed',
        top: 0,
        left: 0,
        backgroundColor: 'rgba(0, 0, 0, 0.5)',
        display: 'flex',
        justifyContent: 'center',
        alignItems: 'center',
        zIndex: 50,
      }}
    >
      <div
        style={{
          backgroundColor: 'white',
          borderRadius: '8px',
          boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)',
          padding: '24px',
          width: '90%',
          maxWidth: '400px',
        }}
      >
        <div
          style={{
            fontSize: '24px',
            fontWeight: 'bold',
            marginBottom: '16px',
          }}
        >
          Verify Mobile No
        </div>
        {!otpSent ? (
          <div style={{ marginBottom: '16px', width: '100%' }}>
            <label
              style={{
                display: 'block',
                marginBottom: '8px',
                fontSize: '14px',
                fontWeight: '500',
              }}
            >
              Enter your phone number with country code:
            </label>
            <input
              type="text"
              placeholder="+91xxxxxxxxxx"
              value={phoneNumber}
              onChange={(e) => setPhoneNumber(e.target.value)}
              style={{
                width: '94%',
                padding: '8px 12px',
                border: '1px solid #d1d5db',
                borderRadius: '4px',
                fontSize: '16px',
              }}
            />
            <div
              style={{
                flexDirection: 'row',
                justifyContent: 'flex-end',
                marginTop: '16px',
              }}
            >
              <button
                onClick={sendOtp}
                style={{
                  width: '100px',
                  backgroundColor: 'black',
                  color: 'white',
                  padding: '10px',
                  borderRadius: '4px',
                  border: 'none',
                  cursor: 'pointer',
                  opacity: 1,
                }}
              >
                Send OTP
              </button>
            </div>
            {error && <p style={{ color: 'red' }}>{error}</p>}
          </div>
        ) : (
          <div style={{ marginBottom: '16px' }}>
            <label
              style={{
                display: 'block',
                marginBottom: '8px',
                fontSize: '14px',
                fontWeight: '500',
              }}
            >
              Enter the OTP sent to {phoneNumber}:
            </label>
            <input
              type="text"
              placeholder="Enter OTP"
              value={otp}
              onChange={(e) => setOtp(e.target.value)}
              style={{
                width: '100%',
                padding: '8px 12px',
                border: '1px solid #d1d5db',
                borderRadius: '4px',
                fontSize: '16px',
              }}
            />
            <button
              onClick={verifyOtp}
              style={{
                width: '100px',
                backgroundColor: 'black',
                color: 'white',
                padding: '10px',
                borderRadius: '4px',
                border: 'none',
                cursor: 'pointer',
                opacity: 1,
              }}
            >
              Verify OTP
            </button>
            {error && <p style={{ color: 'red' }}>{error}</p>}
          </div>
        )}
        {success && <p style={{ color: 'green' }}>{success}</p>}
      </div>
    </div>
  );
}

Approach 2:
where i use signwith otp and able to send otp and verify the phone no but not able to update user.
to update user i have build a superbase edge function but not able to update the user
code component otpVerfication:

import { useState } from 'react';
import { createClient } from '@supabase/supabase-js';

const supabase = createClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
);

export function OtpVerificationComponent({ userEmail, userId }) {
  const [phoneNumber, setPhoneNumber] = useState('');
  const [otpSent, setOtpSent] = useState(false);
  const [otp, setOtp] = useState('');
  const [error, setError] = useState('');
  const [success, setSuccess] = useState('');
  
  console.log(`usermail ${userEmail} userIdc ${userId}`)
  const sendOtp = async () => {
    if (!userEmail || !userId) {
      setError('No logged-in user. Please log in first.');
      return;
    }

    try {
      const { error } = await supabase.auth.signInWithOtp({
        phone: phoneNumber,
      });

      if (error) {
        setError(`Failed to send OTP: ${error.message}`);
      } else {
        setOtpSent(true);
        setError('');
      }
    } catch (error) {
      setError(`An error occurred while sending OTP: ${error.message}`);
    }
  };

  const verifyOtp = async () => {
    if (!userEmail || !userId) {
      setError('No logged-in user. Please log in first.');
      return;
    }
  
    try {
      const { data, error } = await supabase.auth.verifyOtp({
        phone: phoneNumber,
        token: otp,
        type: 'sms',
      });
  
      if (error) {
        setError(`Invalid OTP: ${error.message}`);
      } else {
        setSuccess('Phone number verified successfully');
        setError('');
  
        try {
          const response = await fetch(
            'https://ldgbeyuusmyliblkcnyh.supabase.co/functions/v1/updatePhone', 
            {
              method: 'POST',
              headers: {
                'Content-Type': 'application/json',
                'Authorization': `Bearer ${process.env.SUPABASE_SERVICE_UPDATE_PHONE_AUTH_TOKEN}`
              },
              body: JSON.stringify({ userId, phone: phoneNumber }),
            }
          );
  
          const result = await response.json();
  
          if (response.ok) {
            setSuccess('Phone number updated successfully.');
          } else {
            setError(`Failed to update phone number: ${result.error}`);
          }
        } catch (updateError) {
          setError('An error occurred while updating the phone number.');
          console.log(updateError);
        }
      }
    } catch (error) {
      setError('An error occurred while verifying OTP: ' + error.message);
    }
  };

  return (
    <div style={{
      width: '100%', 
      height: '100%', 
      position: 'fixed', 
      top: 0, 
      left: 0, 
      backgroundColor: 'rgba(0, 0, 0, 0.5)',
      display: 'flex',
      justifyContent: 'center',
      alignItems: 'center',
      zIndex: 50
    }}>
      <div style={{
        backgroundColor: 'white',
        borderRadius: '8px',
        boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)',
        padding: '24px',
        width: '90%',
        maxWidth: '400px'
      }}>
        <div style={{ fontSize: '24px', fontWeight: 'bold', marginBottom: '16px' }}>Verify Mobile No</div>
        {!otpSent ? (
          <div style={{ marginBottom: '16px', width:'100%' }}>
            <label style={{ display: 'block', marginBottom: '8px', fontSize: '14px', fontWeight: '500' }}>
              Enter your phone number with country code:
            </label>
            <input
              type="text"
              placeholder="+91xxxxxxxxxx"
              value={phoneNumber}
              onChange={(e) => setPhoneNumber(e.target.value)}
              style={{
                width: '94%',
                padding: '8px 12px',
                border: '1px solid #d1d5db',
                borderRadius: '4px',
                fontSize: '16px'
              }}
            />
            <div style={{ flexDirection: "row", justifyContent: "flex-end", marginTop: '16px' }}>
              <button onClick={sendOtp} style={{
                width: '100px',
                backgroundColor: 'black',
                color: 'white',
                padding: '10px',
                borderRadius: '4px',
                border: 'none',
                cursor: 'pointer',
                opacity: 1
              }}>Send OTP</button>
            </div>
            {error && <p style={{ color: 'red' }}>{error}</p>}
          </div>
        ) : (
          <div style={{ marginBottom: '16px' }}>
            <label style={{ display: 'block', marginBottom: '8px', fontSize: '14px', fontWeight: '500' }}>
              Enter the OTP sent to {phoneNumber}:
            </label>
            <input
              type="text"
              placeholder="Enter OTP"
              value={otp}
              onChange={(e) => setOtp(e.target.value)}
              style={{
                width: '100%',
                padding: '8px 12px',
                border: '1px solid #d1d5db',
                borderRadius: '4px',
                fontSize: '16px'
              }}
            />
            <button onClick={verifyOtp} style={{
              width: '100px',
              backgroundColor: 'black',
              color: 'white',
              padding: '10px',
              borderRadius: '4px',
              border: 'none',
              cursor: 'pointer',
              opacity: 1
            }}>Verify OTP</button>
            {error && <p style={{ color: 'red' }}>{error}</p>}
          </div>
        )}
        {success && <p style={{ color: 'green' }}>{success}</p>}
      </div>
    </div>
  );
}

edge function:

import { createClient } from "jsr:@supabase/supabase-js@2";
const corsHeaders = {
  "Access-Control-Allow-Origin": "*",
  "Access-Control-Allow-Headers":
    "authorization, x-client-info, apikey, content-type",
};

console.log(`Function "updatePhoneNumber" up and running!`);

Deno.serve(async (req) => {
  // Handle CORS preflight requests
  if (req.method === "OPTIONS") {
    return new Response("ok", { headers: corsHeaders });
  }

  try {
    const { userId, phone } = await req.json();
    
    if (!userId || !phone) {
      throw new Error("userId and phone are required");
    }
    
    const supabaseUrl = Deno.env.get("SUPABASE_URL");
    const supabaseServiceKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY");
    const supabase = createClient(supabaseUrl, supabaseServiceKey);
    
    // Format phone number
    const formattedPhone = phone.toString().startsWith('+') ? phone.toString() : `+${phone}`;
    
    // Update phone in user metadata - this is the most reliable approach
    const { data, error } = await supabase.auth.admin.updateUserById(userId, {
      user_metadata: { 
        phone: formattedPhone, 
        phone_verified: true 
      }
    });

    if (error) {
      throw error;
    }
    
    // Also store in a profiles table if you have one
    try {
      await supabase
        .from('profiles')  // Change 'profiles' to your actual table name if different
        .upsert(
          { 
            id: userId, 
            phone: formattedPhone,
            updated_at: new Date().toISOString()
          }, 
          { onConflict: 'id' }
        );
    } catch (profileError) {
      // Continue even if profile update fails
      console.error("Profile update error:", profileError);
    }

    return new Response(JSON.stringify({
      success: true,
      message: "Phone number updated successfully",
      userId: userId,
      phone: formattedPhone
    }), {
      headers: { ...corsHeaders, "Content-Type": "application/json" },
      status: 200,
    });
  } catch (error) {
    console.error("Error:", error);
    return new Response(JSON.stringify({ 
      success: false,
      error: error.message || "Unknown error"
    }), {
      headers: { ...corsHeaders, "Content-Type": "application/json" },
      status: 400,
    });
  }
});

references:

  1. JavaScript API Reference | Supabase Docs
  2. JavaScript API Reference | Supabase Docs

please help me to update the user phone no with otp verification