This commit is contained in:
debu-2 2026-10-13 01:33:49 +05:30
parent bb9fc9a9cf
commit 9dbda69db1
5 changed files with 222 additions and 3 deletions

View file

@ -10,6 +10,7 @@ import {
type AdminTemplateConfig,
type AdminUrmiOtpProject,
} from '../../lib/api'
import { AdminTokensPage } from './AdminTokensPage'
import { AdminWhatsAppPage } from './AdminWhatsAppPage'
const KYC_DOC_LABELS: Record<string, string> = {
@ -392,7 +393,7 @@ function AdminCompliance() {
export function AdminApp() {
const [authenticated, setAuthenticated] = useState<boolean | null>(null)
const [section, setSection] = useState<'compliance' | 'whatsapp'>('compliance')
const [section, setSection] = useState<'compliance' | 'whatsapp' | 'tokens'>('compliance')
useEffect(() => {
adminApi.me().then(() => setAuthenticated(true)).catch(() => setAuthenticated(false))
@ -426,6 +427,13 @@ export function AdminApp() {
>
WhatsApp Business
</button>
<button
type="button"
className={section === 'tokens' ? 'active' : ''}
onClick={() => setSection('tokens')}
>
Tokens
</button>
</nav>
<button className="secondary-button" onClick={logout}>Log out</button>
</div>
@ -433,7 +441,7 @@ export function AdminApp() {
</header>
<main>
{authenticated
? (section === 'compliance' ? <AdminCompliance /> : <AdminWhatsAppPage />)
? (section === 'compliance' ? <AdminCompliance /> : section === 'tokens' ? <AdminTokensPage /> : <AdminWhatsAppPage />)
: <AdminLogin onSuccess={() => setAuthenticated(true)} />}
</main>
</div>

View file

@ -0,0 +1,103 @@
import { Copy } from 'lucide-react'
import { useEffect, useState } from 'react'
import { adminApi, type AdminConnectionTokens } from '../../lib/api'
type UserRow = AdminConnectionTokens['users'][number]
type WhatsAppAccount = UserRow['whatsapp_accounts'][number]
function TokenValue({ label, value }: { label: string; value: string }) {
const [copied, setCopied] = useState(false)
const shown = value || 'Not saved'
async function copy() {
if (!value) return
await navigator.clipboard.writeText(value)
setCopied(true)
window.setTimeout(() => setCopied(false), 1500)
}
return (
<div className="admin-token-value">
<div className="admin-token-label">
<strong>{label}</strong>
{value && (
<button type="button" className="secondary-button" onClick={() => void copy()}>
<Copy size={14} /> {copied ? 'Copied' : 'Copy'}
</button>
)}
</div>
<code>{shown}</code>
</div>
)
}
function WhatsAppTokens({ account }: { account: WhatsAppAccount }) {
const phoneIds = account.phones.map((phone) => phone.phone_number_id).filter(Boolean)
return (
<article className="admin-token-account">
<div className="panel-heading">
<div>
<h3>{account.name}</h3>
<p>{account.source === 'token' ? 'Pasted token' : 'Facebook signup'} · {account.status}</p>
</div>
</div>
<TokenValue label="Access token" value={account.access_token} />
<TokenValue label="WhatsApp Business account ID" value={account.waba_id} />
<TokenValue label="Phone number ID" value={phoneIds.join('\n')} />
{account.phones.length > 0 && (
<p className="admin-token-phones">
{account.phones.map((phone) => phone.display_phone || phone.phone_number_id).join(' · ')}
</p>
)}
</article>
)
}
export function AdminTokensPage() {
const [users, setUsers] = useState<UserRow[]>([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState('')
useEffect(() => {
adminApi.connectionTokens()
.then((payload) => setUsers(payload.users))
.catch((reason) => setError(reason instanceof Error ? reason.message : 'Could not load tokens'))
.finally(() => setLoading(false))
}, [])
if (loading) return <div className="auth-loading">Loading tokens…</div>
return (
<div className="admin-content">
<div className="account-heading">
<span className="eyebrow">Connections</span>
<h1>Saved tokens</h1>
<p>Each customer account, with the WhatsApp access token, account ID, and phone number ID, plus the Facebook token when one is saved.</p>
</div>
{error && <p className="auth-error">{error}</p>}
{!error && users.length === 0 && <section className="panel placeholder"><p>No tokens have been saved yet.</p></section>}
{users.map((user) => (
<section className="panel admin-kyc-card" key={user.id}>
<div className="panel-heading">
<div>
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
</div>
{user.whatsapp_accounts.map((account) => <WhatsAppTokens key={account.id} account={account} />)}
{user.facebook && (
<article className="admin-token-account">
<div className="panel-heading">
<div>
<h3>Facebook</h3>
<p>{user.facebook.user_name || 'Facebook user'} · {user.facebook.status}</p>
</div>
</div>
<TokenValue label="Access token" value={user.facebook.access_token} />
</article>
)}
</section>
))}
</div>
)
}

View file

@ -864,6 +864,14 @@ main { padding: 30px clamp(20px, 3vw, 44px) 50px; }
.admin-login-page { min-height: calc(100vh - 72px); display: grid; place-items: center; }
.admin-login-card { width: min(420px, 100%); }
.admin-kyc-card { margin-bottom: 18px; }
.admin-token-account { margin-top: 14px; padding-top: 12px; border-top: 1px solid #e6ebf2; }
.admin-token-account h3 { margin: 0; color: #14233b; font-size: 16px; }
.admin-token-account .panel-heading p { margin: 4px 0 0; }
.admin-token-value { display: grid; gap: 6px; margin-top: 12px; }
.admin-token-label { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.admin-token-label strong { color: #35435a; font-size: 13px; }
.admin-token-value code { display: block; white-space: pre-wrap; word-break: break-all; padding: 10px 12px; border-radius: 8px; background: #f6f8fb; border: 1px solid #e5eaf1; color: #17253e; font-size: 12px; }
.admin-token-phones { margin: 8px 0 0; color: #6d7c90; font-size: 12px; }
.admin-actions { display: flex; gap: 8px; }
/* Message settings accordion */

View file

@ -948,6 +948,31 @@ export type AdminWhatsAppSendPayload = {
reaction_emoji?: string
}
export type AdminConnectionTokens = {
users: Array<{
id: string
name: string
email: string
whatsapp_accounts: Array<{
id: string
name: string
status: string
source: 'token' | 'signup'
access_token: string
waba_id: string
phones: Array<{ id: string; display_phone: string; phone_number_id: string; status: string }>
updated_at: string
}>
facebook: {
status: string
user_name: string
access_token: string
expires_at: string | null
connected_at: string | null
} | null
}>
}
export const adminApi = {
me: () => jsonRequest<{ username: string }>('/api/v1/admin/me'),
login: (username: string, password: string) =>
@ -1008,6 +1033,7 @@ export const adminApi = {
method: 'PATCH',
body: JSON.stringify(payload),
}),
connectionTokens: () => jsonRequest<AdminConnectionTokens>('/api/v1/admin/connection-tokens'),
whatsappConfig: () => jsonRequest<AdminWhatsAppConfig>('/api/v1/admin/whatsapp/config'),
updateWhatsAppConfig: (payload: {
phone_number_id?: string