token
This commit is contained in:
parent
bb9fc9a9cf
commit
9dbda69db1
5 changed files with 222 additions and 3 deletions
|
|
@ -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>
|
||||
|
|
|
|||
103
frontend/src/features/admin/AdminTokensPage.tsx
Normal file
103
frontend/src/features/admin/AdminTokensPage.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue