Integrate with Lovable

Add Login with Proma and credit-based billing to a Lovable project.

This guide walks through integrating the Proma SDK into a Lovable project. Lovable scaffolds a Vite + React + TypeScript app, so the React bindings are the fastest path.

Step 1 — Register your app on Proma

  1. Sign in to Proma and open the Developer Portal.
  2. Click New App, give it a name, and add your redirect URI — for a Lovable preview this is usually https://preview--your-project.lovable.app/auth/callback. For a custom domain, use https://yourdomain.com/auth/callback.
  3. Copy the Client ID — you will paste it into Lovable in the next step.

You can add additional redirect URIs later (for example your local dev URL) from the app detail page.

Step 2 — Ask Lovable to install the SDK

In the Lovable chat, paste:

Install @proma/sdk and wrap the app in PromaProvider.
Client ID: proma_app_your_client_id
Redirect URI: https://preview--your-project.lovable.app/auth/callback
Scopes: profile, credits

Lovable will add the dependency to package.json and modify your root component.

If you prefer to do it by hand, the resulting main.tsx looks like this:

import { PromaProvider } from '@proma/sdk/react'
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import App from './App.tsx'
import './index.css'

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <PromaProvider
      clientId="proma_app_your_client_id"
      redirectUri={`${window.location.origin}/auth/callback`}
      scopes={['profile', 'credits']}
    >
      <App />
    </PromaProvider>
  </StrictMode>,
)

Step 3 — Add the login button

Drop the LoginWithProma button anywhere in your UI. It handles the redirect and loading state for you.

import { LoginWithProma, usePromaAuth } from '@proma/sdk/react'

export function Header() {
  const { user, isAuthenticated } = usePromaAuth()

  if (isAuthenticated) {
    return <span>Signed in as {user?.email}</span>
  }

  return <LoginWithProma />
}

Step 4 — Handle the OAuth callback

Lovable projects use react-router. Add a route at /auth/callback that finishes the PKCE exchange:

import { useEffect } from 'react'
import { useNavigate } from 'react-router-dom'
import { usePromaClient } from '@proma/sdk/react'

export function AuthCallback() {
  const proma = usePromaClient()
  const navigate = useNavigate()

  useEffect(() => {
    proma.handleCallback().then(() => navigate('/', { replace: true }))
  }, [proma, navigate])

  return <p>Signing you in…</p>
}

Register it alongside your other routes:

<Route path="/auth/callback" element={<AuthCallback />} />

Step 5 — Charge credits from your app

Every action that costs credits should be wrapped in a charge call. Proma debits the user's wallet and credits your developer balance.

import { usePromaClient } from '@proma/sdk/react'

export function GenerateButton() {
  const proma = usePromaClient()

  async function onClick() {
    const { remaining } = await proma.charge({ credits: 1 })
    console.log(`User has ${remaining} credits left`)
    // …run your paid logic here
  }

  return <button onClick={onClick}>Generate (1 credit)</button>
}

If the user is out of credits, charge throws a PromaInsufficientCreditsError — catch it to prompt a top-up.

Step 6 — Ship to Lovable

  1. Commit via the Lovable UI.
  2. On the first deploy, copy the preview URL (https://preview--your-project.lovable.app) and add it as an allowed redirect URI in the developer portal if you have not already.
  3. Test the full login → charge flow end-to-end in the deployed preview.

Troubleshooting

  • redirect_uri_mismatch — the URI you passed to PromaProvider must exactly match one registered in the developer portal, including the scheme and trailing path.
  • Blank screen after redirect — make sure the /auth/callback route is registered and reachable before any auth guard redirects.
  • crypto.subtle is undefined — the SDK relies on SubtleCrypto for PKCE. Lovable's preview runs over HTTPS, so this works in production; for local testing, use http://localhost (allowed) rather than a non-localhost HTTP origin.

Next steps