access fix

This commit is contained in:
debu-2 2026-10-13 01:18:08 +05:30
parent 73ce8c5f95
commit bb9fc9a9cf
8 changed files with 309 additions and 192 deletions

View file

@ -1,6 +1,7 @@
import { Check, ChevronLeft, Copy, ExternalLink, Loader2, MessageCircle, MoreHorizontal, Plus, RefreshCw } from 'lucide-react'
import { Link } from 'react-router-dom'
import { CustomerWhatsAppInbox } from './CustomerWhatsAppInbox'
import { useEffect, useRef, useState, type FormEvent } from 'react'
import { useEffect, useRef, useState } from 'react'
import {
whatsappApi,
type WhatsAppAccount,
@ -138,10 +139,6 @@ export function WhatsAppSetupPage() {
const [developer, setDeveloper] = useState<WhatsAppDeveloperConfig | null>(null)
const [logs, setLogs] = useState<WhatsAppAccountLogs | null>(null)
const [copied, setCopied] = useState(false)
const [showToken, setShowToken] = useState(false)
const [tokenValue, setTokenValue] = useState('')
const [wabaId, setWabaId] = useState('')
const [phoneNumberId, setPhoneNumberId] = useState('')
const [pins, setPins] = useState<Record<string, string>>({})
const sessionRef = useRef<SignupSession | null>(null)
const codeRef = useRef<string | null>(null)
@ -261,41 +258,6 @@ export function WhatsAppSetupPage() {
reportRef.current = reportSession
finishRef.current = finishIfReady
function mergeAccounts(incoming: WhatsAppAccount[]) {
if (!incoming.length) return
setAccounts((current) => {
const byId = new Map(incoming.map((account) => [account.id, account]))
const merged = current.map((account) => byId.get(account.id) || account)
for (const account of incoming) {
if (!merged.some((item) => item.id === account.id)) merged.unshift(account)
}
return merged
})
}
async function submitToken(event: FormEvent) {
event.preventDefault()
setBusy(true)
setError('')
setNotice('')
try {
const payload = await whatsappApi.connectToken({
access_token: tokenValue.trim(),
waba_id: wabaId.trim(),
phone_number_id: phoneNumberId.trim() || null,
})
setResult(payload)
mergeAccounts(payload.accounts)
setTokenValue('')
setShowToken(false)
setPhase('result')
} catch (reason) {
setError(reason instanceof Error ? reason.message : 'WhatsApp could not be connected')
} finally {
setBusy(false)
}
}
async function finishIfReady() {
const session = sessionRef.current
const code = codeRef.current
@ -437,26 +399,9 @@ export function WhatsAppSetupPage() {
</p>
</div>
{accounts.length > 0 && phase !== 'provisioning' && (
<div className="wa-biz-heading-actions">
<button
className="secondary-button"
type="button"
disabled={busy}
onClick={() => {
if (phase === 'list' && showToken) {
setShowToken(false)
return
}
setShowToken(true)
if (phase === 'result') setPhase('list')
}}
>
{phase === 'list' && showToken ? 'Hide token setup' : 'Use a token'}
</button>
<button className="primary-button" type="button" onClick={() => void startEmbeddedSignup()} disabled={busy}>
<Plus size={16} /> Connect account
</button>
</div>
<button className="primary-button" type="button" onClick={() => void startEmbeddedSignup()} disabled={busy}>
<Plus size={16} /> Connect account
</button>
)}
</div>
</div>
@ -480,10 +425,7 @@ export function WhatsAppSetupPage() {
{busy ? <Loader2 size={16} className="wa-spin" /> : null}
Continue with Facebook
</button>
<button className="text-button wa-token-toggle" type="button" onClick={() => setShowToken((open) => !open)}>
{showToken ? 'Hide token setup' : 'Use an access token instead'}
</button>
{showToken && <TokenConnectForm embedded busy={busy} tokenValue={tokenValue} wabaId={wabaId} phoneNumberId={phoneNumberId} onToken={setTokenValue} onWaba={setWabaId} onPhone={setPhoneNumberId} onSubmit={submitToken} />}
<Link className="text-button wa-token-toggle" to="/console/whatsapp/token">Have an access token? Connect it separately</Link>
<small>
{config?.ready
? 'Setup usually takes a few minutes. Facebook decides which businesses, accounts, and numbers you can use.'
@ -546,10 +488,6 @@ export function WhatsAppSetupPage() {
</section>
)}
{phase === 'list' && showToken && (
<TokenConnectForm busy={busy} tokenValue={tokenValue} wabaId={wabaId} phoneNumberId={phoneNumberId} onToken={setTokenValue} onWaba={setWabaId} onPhone={setPhoneNumberId} onSubmit={submitToken} />
)}
{phase === 'list' && (
<div className="wa-biz-list">
{accounts.map((account) => (
@ -819,56 +757,6 @@ export function WhatsAppSetupPage() {
)
}
function TokenConnectForm({
embedded = false,
busy,
tokenValue,
wabaId,
phoneNumberId,
onToken,
onWaba,
onPhone,
onSubmit,
}: {
embedded?: boolean
busy: boolean
tokenValue: string
wabaId: string
phoneNumberId: string
onToken: (value: string) => void
onWaba: (value: string) => void
onPhone: (value: string) => void
onSubmit: (event: FormEvent) => void
}) {
return (
<form className={embedded ? 'wa-token-embed' : 'wa-biz-card wa-token-card'} onSubmit={onSubmit}>
<h2>Use your own access token</h2>
<p>Paste the token and WhatsApp Business account ID from Meta. Urmi stores them for this workspace and does not show the token again.</p>
<div className="wa-token-form">
<label className="explorer-input">
<strong>Access token</strong>
<span>A token that can manage this WhatsApp account and send messages.</span>
<input type="password" value={tokenValue} onChange={(event) => onToken(event.target.value)} autoComplete="off" required />
</label>
<label className="explorer-input">
<strong>WhatsApp Business account ID</strong>
<span>The account ID Meta shows for this business.</span>
<input value={wabaId} onChange={(event) => onWaba(event.target.value)} inputMode="numeric" required />
</label>
<label className="explorer-input">
<strong>Phone number ID</strong>
<span>Optional. Add it if you want a specific number connected with this account.</span>
<input value={phoneNumberId} onChange={(event) => onPhone(event.target.value)} inputMode="numeric" />
</label>
<button className="primary-button" type="submit" disabled={busy}>
{busy ? <Loader2 size={16} className="wa-spin" /> : null}
Save and connect
</button>
</div>
</form>
)
}
function StatusDot({ status, label }: { status: string; label?: string }) {
const tone = status === 'connected' || status === 'active' ? 'ok' : status === 'unavailable' || status === 'failed' || status === 'expired' || status === 'disconnected' ? 'bad' : 'wait'
return (

View file

@ -0,0 +1,86 @@
import { Loader2 } from 'lucide-react'
import { useState, type FormEvent } from 'react'
import { Link } from 'react-router-dom'
import { whatsappApi, type WhatsAppSignupResult } from '../../lib/api'
export function WhatsAppTokenPage() {
const [token, setToken] = useState('')
const [wabaId, setWabaId] = useState('')
const [phoneNumberId, setPhoneNumberId] = useState('')
const [busy, setBusy] = useState(false)
const [error, setError] = useState('')
const [result, setResult] = useState<WhatsAppSignupResult | null>(null)
async function submit(event: FormEvent) {
event.preventDefault()
setBusy(true)
setError('')
setResult(null)
try {
const payload = await whatsappApi.connectToken({
access_token: token.trim(),
waba_id: wabaId.trim(),
phone_number_id: phoneNumberId.trim() || null,
})
setResult(payload)
setToken('')
if (payload.status === 'failed' || payload.status === 'expired') setError(payload.message)
} catch (reason) {
setError(reason instanceof Error ? reason.message : 'This token could not be saved')
} finally {
setBusy(false)
}
}
const account = result?.accounts[0]
return (
<div className="wa-biz wa-token-page">
<div className="account-heading">
<span className="eyebrow">Messages</span>
<h1>WhatsApp token</h1>
<p>Paste a token from Meta. This is separate from Facebook signup and does not ask Meta for signup-only fields.</p>
</div>
{error && <p className="auth-error">{error}</p>}
<form className="wa-biz-card" onSubmit={submit}>
<div className="wa-token-form">
<label className="explorer-input">
<strong>Access token</strong>
<span>From WhatsApp → API Setup, or a system user token that can send on this account.</span>
<input type="password" value={token} onChange={(event) => setToken(event.target.value)} autoComplete="off" required />
</label>
<label className="explorer-input">
<strong>WhatsApp Business account ID</strong>
<span>The numeric ID on the same API Setup page.</span>
<input value={wabaId} onChange={(event) => setWabaId(event.target.value)} inputMode="numeric" required />
</label>
<label className="explorer-input">
<strong>Phone number ID</strong>
<span>Optional. Use it when the account has more than one number.</span>
<input value={phoneNumberId} onChange={(event) => setPhoneNumberId(event.target.value)} inputMode="numeric" />
</label>
<button className="primary-button" type="submit" disabled={busy}>
{busy ? <Loader2 size={16} className="wa-spin" /> : null}
Save token
</button>
</div>
</form>
{result && !error && (
<section className="wa-biz-card wa-token-result">
<h2>{result.status === 'connected' ? 'Token saved' : 'Token saved, one step left'}</h2>
<p>{result.message}</p>
{account && (
<dl className="wa-biz-facts">
<div><dt>Account</dt><dd>{account.name}</dd></div>
<div><dt>Phone</dt><dd>{account.primary_phone?.display_phone || account.primary_phone?.phone_number_id || 'Not added yet'}</dd></div>
</dl>
)}
<Link className="primary-button" to="/console/whatsapp">Open WhatsApp</Link>
</section>
)}
</div>
)
}