feat(docs, integration): update integration documentation and redirect legacy paths

Introduced a new public documentation site for client integration at `/docs` and `/docs/integration`, removing the need for admin login. Updated the integration guide to redirect from the old admin path to the new documentation site. Added localization support for the integration documentation in English, Nepali, and Chinese. Enhanced the layout structure and improved the handling of currency display in settlement bills.
This commit is contained in:
2026-06-15 11:08:19 +08:00
parent e7b72cfdca
commit 17335cb47a
35 changed files with 2668 additions and 436 deletions

View File

@@ -49,7 +49,7 @@ const HUB_CARDS: HubCard[] = [
requiredAny: PRD_INTEGRATION_ACCESS_ANY,
},
{
href: "/admin/docs/integration-guide",
href: "/docs/integration",
titleKey: "hub.integrationGuideTitle",
descKey: "hub.integrationGuideDesc",
requiredAny: PRD_INTEGRATION_ACCESS_ANY,

View File

@@ -1,414 +1,6 @@
import { Card, CardContent } from "@/components/ui/card";
import { redirect } from "next/navigation";
const sections = [
{ id: "overview", title: "1. 文档说明" },
{ id: "preparation", title: "2. 接入前准备" },
{ id: "sso", title: "3. SSO 登录接入" },
{ id: "wallet", title: "4. 钱包接口" },
{ id: "errors", title: "5. 错误码与幂等" },
{ id: "testing", title: "6. 联调与上线" },
{ id: "appendix", title: "7. 附录" },
] as const;
const ssoFields = [
["site_code", "string", "是", "站点编码,由我方提供。"],
["user_id", "string", "是", "客户侧用户唯一标识。"],
["username", "string", "是", "客户侧用户名或展示名。"],
["timestamp", "number", "是", "发起时间戳,单位秒。"],
["nonce", "string", "是", "随机串,用于防重放。"],
["currency", "string", "否", "币种,如 CNY。"],
["device", "string", "否", "终端类型,如 h5、web。"],
] as const;
const walletCommonFields = [
["site_code", "string", "站点编码"],
["user_id", "string", "客户侧用户唯一标识"],
["transaction_id", "string", "唯一交易号,幂等主键"],
["timestamp", "number", "请求时间戳,单位秒"],
["sign", "string", "签名结果"],
] as const;
const errorRows = [
["0", "成功", "按成功结果落账或继续业务。"],
["1001", "参数错误", "检查字段完整性、类型、必填项。"],
["1002", "签名错误", "检查签名原串、密钥、时间戳。"],
["1003", "用户不存在", "确认用户标识是否正确。"],
["1004", "余额不足", "前端提示余额不足,不继续下注。"],
["1006", "重复交易", "返回首次结果,不允许重复记账。"],
["1099", "系统异常", "可按约定策略重试,并保留日志。"],
] as const;
function DocTable({
headers,
rows,
}: {
headers: readonly string[];
rows: readonly (readonly string[])[];
}): React.ReactElement {
return (
<div className="overflow-x-auto rounded-lg border border-border">
<table className="w-full min-w-[640px] border-collapse text-sm">
<thead className="bg-muted/50 text-left">
<tr>
{headers.map((header) => (
<th key={header} className="border-b border-border px-4 py-3 font-medium text-foreground">
{header}
</th>
))}
</tr>
</thead>
<tbody>
{rows.map((row, rowIndex) => (
<tr key={`${row[0]}-${rowIndex}`} className="align-top">
{row.map((cell, cellIndex) => (
<td
key={`${row[0]}-${cellIndex}`}
className="border-b border-border px-4 py-3 leading-6 text-muted-foreground"
>
{cell}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
);
/** @deprecated 单页长文档已拆分为 /docs/integration/* 多页;保留导出避免旧引用报错 */
export function IntegrationGuideScreen(): never {
redirect("/docs/integration");
}
export function IntegrationGuideScreen(): React.ReactElement {
return (
<div className="mx-auto flex w-full max-w-[1280px] min-w-0 flex-col px-4 py-6 sm:px-6 lg:px-8 lg:py-8">
<div className="grid gap-10 xl:grid-cols-[220px_minmax(0,1fr)]">
<aside className="hidden xl:block">
<div className="sticky top-6 space-y-4 text-sm">
<div>
<div className="text-xs text-muted-foreground"></div>
<div className="mt-2 font-medium text-foreground"></div>
</div>
<nav className="space-y-1 border-l border-border pl-4">
{sections.map((section) => (
<a
key={section.id}
href={`#${section.id}`}
className="block py-1 text-muted-foreground transition hover:text-foreground"
>
{section.title}
</a>
))}
</nav>
</div>
</aside>
<main className="min-w-0">
<header className="border-b border-border pb-8">
<h1 className="text-3xl font-semibold tracking-tight text-foreground"></h1>
<p className="mt-4 max-w-3xl text-sm leading-7 text-muted-foreground">
线
</p>
</header>
<div className="mt-8 space-y-10">
<section id="overview" className="scroll-mt-24 space-y-5 border-b border-border pb-10">
<div>
<h2 className="text-2xl font-semibold tracking-tight">1. </h2>
<p className="mt-3 text-sm leading-7 text-muted-foreground">
SSO
</p>
</div>
<div className="grid gap-4 md:grid-cols-3">
<div className="rounded-lg border border-border px-5 py-5">
<div className="text-sm font-medium text-foreground"></div>
<div className="mt-2 text-sm leading-6 text-muted-foreground">
H5WebApp WebView
</div>
</div>
<div className="rounded-lg border border-border px-5 py-5">
<div className="text-sm font-medium text-foreground"></div>
<div className="mt-2 text-sm leading-6 text-muted-foreground">
</div>
</div>
<div className="rounded-lg border border-border px-5 py-5">
<div className="text-sm font-medium text-foreground"></div>
<div className="mt-2 text-sm leading-6 text-muted-foreground">
</div>
</div>
</div>
<div className="rounded-lg border border-border px-5 py-5">
<div className="text-sm font-medium text-foreground"></div>
<ol className="mt-3 space-y-2 text-sm leading-6 text-muted-foreground">
<li>1. </li>
<li>2. SSO token</li>
<li>3. </li>
<li>4. </li>
<li>5. </li>
<li>6. </li>
</ol>
</div>
</section>
<section id="preparation" className="scroll-mt-24 space-y-5 border-b border-border pb-10">
<div>
<h2 className="text-2xl font-semibold tracking-tight">2. </h2>
<p className="mt-3 text-sm leading-7 text-muted-foreground">
</p>
</div>
<div className="grid gap-4 lg:grid-cols-2">
<div className="rounded-lg border border-border px-5 py-5">
<div className="text-base font-medium text-foreground"></div>
<ul className="mt-4 space-y-2 text-sm leading-6 text-muted-foreground">
<li> </li>
<li> </li>
<li> </li>
<li> </li>
</ul>
</div>
<div className="rounded-lg border border-border px-5 py-5">
<div className="text-base font-medium text-foreground"></div>
<ul className="mt-4 space-y-2 text-sm leading-6 text-muted-foreground">
<li> SSO JWT </li>
<li> </li>
<li> </li>
<li> </li>
</ul>
</div>
</div>
</section>
<section id="sso" className="scroll-mt-24 space-y-5 border-b border-border pb-10">
<div>
<h2 className="text-2xl font-semibold tracking-tight">3. SSO </h2>
<p className="mt-3 text-sm leading-7 text-muted-foreground">
</p>
</div>
<div className="rounded-lg border border-border px-5 py-5">
<div className="text-base font-medium text-foreground"></div>
<ol className="mt-4 space-y-2 text-sm leading-6 text-muted-foreground">
<li>1. </li>
<li>2. SSO </li>
<li>3. 使</li>
<li>4. </li>
<li>5. </li>
</ol>
</div>
<div>
<div className="mb-3 text-base font-medium text-foreground">SSO </div>
<DocTable headers={["字段", "类型", "必填", "说明"]} rows={ssoFields} />
</div>
<div className="grid gap-4 lg:grid-cols-2">
<div className="rounded-lg border border-border px-5 py-5">
<div className="text-base font-medium text-foreground"></div>
<ul className="mt-4 space-y-2 text-sm leading-6 text-muted-foreground">
<li> token 60 300 </li>
<li> <code className="rounded bg-muted px-1.5 py-0.5 text-[12px]">user_id</code> </li>
<li> </li>
<li> </li>
</ul>
</div>
<Card className="rounded-lg border-border shadow-none">
<CardContent className="p-5">
<div className="text-sm font-medium text-foreground">SSO </div>
<pre className="mt-4 overflow-x-auto whitespace-pre-wrap break-words rounded-md bg-muted px-4 py-4 text-sm leading-7 text-muted-foreground">{`{
"site_code": "demo",
"user_id": "100001",
"username": "demo_user",
"timestamp": 1718000000,
"nonce": "N8F2X9Q1",
"currency": "CNY",
"device": "h5"
}`}</pre>
</CardContent>
</Card>
</div>
</section>
<section id="wallet" className="scroll-mt-24 space-y-5 border-b border-border pb-10">
<div>
<h2 className="text-2xl font-semibold tracking-tight">4. </h2>
<p className="mt-3 text-sm leading-7 text-muted-foreground">
</p>
</div>
<div className="grid gap-4 xl:grid-cols-3">
<div className="rounded-lg border border-border px-5 py-5">
<div className="text-base font-medium text-foreground"></div>
<div className="mt-2 text-sm leading-6 text-muted-foreground">
</div>
<div className="mt-3 rounded-md bg-muted px-3 py-2 text-xs text-foreground">POST /wallet/balance</div>
</div>
<div className="rounded-lg border border-border px-5 py-5">
<div className="text-base font-medium text-foreground"></div>
<div className="mt-2 text-sm leading-6 text-muted-foreground">
</div>
<div className="mt-3 rounded-md bg-muted px-3 py-2 text-xs text-foreground">POST /wallet/debit</div>
</div>
<div className="rounded-lg border border-border px-5 py-5">
<div className="text-base font-medium text-foreground"></div>
<div className="mt-2 text-sm leading-6 text-muted-foreground">
退
</div>
<div className="mt-3 rounded-md bg-muted px-3 py-2 text-xs text-foreground">POST /wallet/credit</div>
</div>
</div>
<div>
<div className="mb-3 text-base font-medium text-foreground"></div>
<DocTable headers={["字段", "类型", "说明"]} rows={walletCommonFields} />
</div>
<div className="grid gap-4 lg:grid-cols-2">
<Card className="rounded-lg border-border shadow-none">
<CardContent className="p-5">
<div className="text-sm font-medium text-foreground"></div>
<pre className="mt-4 overflow-x-auto whitespace-pre-wrap break-words rounded-md bg-muted px-4 py-4 text-sm leading-7 text-muted-foreground">{`{
"site_code": "demo",
"user_id": "100001",
"transaction_id": "BET202606100001",
"order_id": "TICKET202606100001",
"amount": "20.00",
"timestamp": 1718000001,
"sign": "xxxxxx"
}`}</pre>
</CardContent>
</Card>
<Card className="rounded-lg border-border shadow-none">
<CardContent className="p-5">
<div className="text-sm font-medium text-foreground"></div>
<pre className="mt-4 overflow-x-auto whitespace-pre-wrap break-words rounded-md bg-muted px-4 py-4 text-sm leading-7 text-muted-foreground">{`{
"code": 0,
"message": "success",
"data": {
"transaction_id": "BET202606100001",
"balance": "980.00"
}
}`}</pre>
</CardContent>
</Card>
</div>
<div className="grid gap-4 lg:grid-cols-2">
<div className="rounded-lg border border-border px-5 py-5">
<div className="text-base font-medium text-foreground"></div>
<ol className="mt-4 space-y-2 text-sm leading-6 text-muted-foreground">
<li>1. </li>
<li>2. <code className="rounded bg-muted px-1.5 py-0.5 text-[12px]">key=value</code> </li>
<li>3. </li>
<li>4. 使 <code className="rounded bg-muted px-1.5 py-0.5 text-[12px]">HMAC-SHA256</code> </li>
</ol>
</div>
<div className="rounded-lg border border-border px-5 py-5">
<div className="text-base font-medium text-foreground"></div>
<ul className="mt-4 space-y-2 text-sm leading-6 text-muted-foreground">
<li> 使 HTTPS</li>
<li> 使 <code className="rounded bg-muted px-1.5 py-0.5 text-[12px]">1000.00</code></li>
<li> <code className="rounded bg-muted px-1.5 py-0.5 text-[12px]">application/json</code></li>
<li> </li>
</ul>
</div>
</div>
</section>
<section id="errors" className="scroll-mt-24 space-y-5 border-b border-border pb-10">
<div>
<h2 className="text-2xl font-semibold tracking-tight">5. </h2>
<p className="mt-3 text-sm leading-7 text-muted-foreground">
</p>
</div>
<div>
<div className="mb-3 text-base font-medium text-foreground"></div>
<DocTable headers={["错误码", "含义", "处理方式"]} rows={errorRows} />
</div>
<div className="rounded-lg border border-border px-5 py-5">
<div className="text-base font-medium text-foreground"></div>
<ul className="mt-4 space-y-2 text-sm leading-6 text-muted-foreground">
<li> <code className="rounded bg-muted px-1.5 py-0.5 text-[12px]">transaction_id</code> </li>
<li> </li>
<li> </li>
<li> </li>
</ul>
</div>
</section>
<section id="testing" className="scroll-mt-24 space-y-5 border-b border-border pb-10">
<div>
<h2 className="text-2xl font-semibold tracking-tight">6. 线</h2>
<p className="mt-3 text-sm leading-7 text-muted-foreground">
线
</p>
</div>
<div className="grid gap-4 lg:grid-cols-2">
<div className="rounded-lg border border-border px-5 py-5">
<div className="text-base font-medium text-foreground"></div>
<ol className="mt-4 space-y-2 text-sm leading-6 text-muted-foreground">
<li>1. </li>
<li>2. SSO </li>
<li>3. </li>
<li>4. </li>
<li>5. </li>
</ol>
</div>
<div className="rounded-lg border border-border px-5 py-5">
<div className="text-base font-medium text-foreground">线</div>
<ul className="mt-4 space-y-2 text-sm leading-6 text-muted-foreground">
<li> </li>
<li> </li>
<li> </li>
<li> </li>
<li> 线</li>
</ul>
</div>
</div>
</section>
<section id="appendix" className="scroll-mt-24 space-y-5 pb-2">
<div>
<h2 className="text-2xl font-semibold tracking-tight">7. </h2>
<p className="mt-3 text-sm leading-7 text-muted-foreground">
</p>
</div>
<div className="grid gap-4 lg:grid-cols-3">
<div className="rounded-lg border border-border px-5 py-5">
<div className="text-base font-medium text-foreground"></div>
<div className="mt-3 text-sm leading-6 text-muted-foreground">
使 <code className="rounded bg-muted px-1.5 py-0.5 text-[12px]">1000.00</code>
</div>
</div>
<div className="rounded-lg border border-border px-5 py-5">
<div className="text-base font-medium text-foreground"></div>
<div className="mt-3 text-sm leading-6 text-muted-foreground">
使 Unix ISO8601
</div>
</div>
<div className="rounded-lg border border-border px-5 py-5">
<div className="text-base font-medium text-foreground"></div>
<div className="mt-3 text-sm leading-6 text-muted-foreground">
UTF-8 JSON
</div>
</div>
</div>
</section>
</div>
</main>
</div>
</div>
);
}

View File

@@ -0,0 +1,154 @@
/** 代码示例(语言无关,三语共用) */
export const SSO_JWT_PAYLOAD_EXAMPLE = `{
"site_code": "demo",
"site_player_id": "100001",
"iat": 1718000000,
"exp": 1718000300
}`;
export const SSO_JWT_SIGN_EXAMPLE = `const header = base64url(JSON.stringify({ alg: "HS256", typ: "JWT" }));
const payload = base64url(JSON.stringify({
site_code: "demo",
site_player_id: "100001",
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + 300,
}));
const sig = hmacSha256Base64url(\`\${header}.\${payload}\`, SSO_JWT_SECRET);
const token = \`\${header}.\${payload}.\${sig}\`;`;
export const SSO_ENTRY_URL = `https://{lottery_host}/?token={JWT}`;
export const SSO_POSTMESSAGE = `iframe.contentWindow.postMessage(
{ type: "MAIN_INIT_TOKEN", token: jwt, source: "main-site" },
"https://lottery.example.com"
);`;
export const PLAYER_ME_REQUEST = `GET /api/v1/player/me
Authorization: Bearer {JWT}
Accept-Language: zh`;
export const PLAYER_ME_SUCCESS = `{
"code": 0,
"msg": "success",
"data": {
"id": 42,
"site_code": "demo",
"site_player_id": "100001",
"auth_source": "main_site_sso",
"funding_mode": "wallet",
"username": null,
"nickname": null,
"default_currency": "NPR",
"status": 0,
"locale": "zh",
"last_login_at": "2026-06-14T10:00:00+00:00",
"created_at": "2026-06-14T10:00:00+00:00"
}
}`;
export const IFRAME_CHILD_READY = `{
"type": "LOTTERY_READY",
"payload": { "url": "https://lottery.example.com/", "userAgent": "..." },
"timestamp": 1718000000000,
"source": "lottery-iframe"
}`;
export const IFRAME_PARENT_INIT = `{
"type": "MAIN_INIT_TOKEN",
"token": "eyJhbGciOiJIUzI1NiIs...",
"timestamp": 1718000000000,
"source": "main-site"
}`;
export const ACCEPTANCE_PLAYER_ME = `curl -sS "https://{lottery_api}/api/v1/player/me" \\
-H "Authorization: Bearer {JWT}" \\
-H "Accept: application/json"`;
export const ACCEPTANCE_WALLET_DEBIT = `curl -sS -X POST "https://{wallet_host}/wallet/debit-for-lottery" \\
-H "Authorization: Bearer {wallet_api_key}" \\
-H "Content-Type: application/json" \\
-d '{
"site_code": "demo",
"site_player_id": "100001",
"player_id": 42,
"currency_code": "NPR",
"amount_minor": 100,
"idempotent_key": "accept-debit-001"
}'`;
export const PLAYER_AUTH_ERROR = `HTTP/1.1 401 Unauthorized
{
"code": 8002,
"msg": "Token 无效或已过期",
"data": null
}`;
export const WALLET_BALANCE_RESPONSE = `{
"success": true,
"data": { "main_balance": 500000, "currency_code": "NPR" }
}`;
export const WALLET_DEBIT_REQUEST = `POST /wallet/debit-for-lottery
Authorization: Bearer {wallet_api_key}
{
"site_code": "demo",
"site_player_id": "100001",
"player_id": 42,
"currency_code": "NPR",
"amount_minor": 2000,
"idempotent_key": "TI-20260610-abc"
}`;
export const WALLET_SUCCESS = `{
"success": true,
"external_ref_no": "MW-001",
"data": { "main_balance": 498000, "currency_code": "NPR" }
}`;
export const WALLET_FAIL = `{
"success": false,
"message": "main balance insufficient",
"data": { "main_balance": 500, "currency_code": "NPR" }
}`;
export const WALLET_CREDIT_REQUEST = `POST /wallet/credit-from-lottery
Authorization: Bearer {wallet_api_key}
{
"site_code": "demo",
"site_player_id": "100001",
"player_id": 42,
"currency_code": "NPR",
"amount_minor": 2000,
"idempotent_key": "TO-20260610-abc"
}`;
export const TRANSFER_IN = `POST /api/v1/wallet/transfer-in
Authorization: Bearer {player_jwt}
{ "amount": 2000, "currency": "NPR", "idempotent_key": "ti-001" }`;
export const TRANSFER_OUT = `POST /api/v1/wallet/transfer-out
Authorization: Bearer {player_jwt}
{ "amount": 1000, "currency": "NPR", "idempotent_key": "to-001" }`;
export const TRANSFER_SUCCESS = `{
"code": 0,
"msg": "success",
"data": {
"transfer_no": "TI20260614001",
"direction": "in",
"currency_code": "NPR",
"amount": 2000,
"status": "success",
"external_ref_no": "debit-for-lottery-1718360000-a1b2c3",
"balance": 2000,
"log_id": "WX_20260614_0001",
"lottery_balance_after": 2000,
"lottery_available_after": 2000,
"finished_at": "2026-06-14T10:00:00+00:00"
}
}`;

View File

@@ -0,0 +1,314 @@
"use client";
import {
DocCode,
DocEndpoint,
DocList,
DocNote,
DocOrderedList,
DocPageHeader,
DocSection,
DocTable,
} from "@/components/docs/doc-ui";
import {
SSO_ENTRY_URL,
SSO_JWT_PAYLOAD_EXAMPLE,
SSO_JWT_SIGN_EXAMPLE,
SSO_POSTMESSAGE,
ACCEPTANCE_PLAYER_ME,
ACCEPTANCE_WALLET_DEBIT,
IFRAME_CHILD_READY,
IFRAME_PARENT_INIT,
PLAYER_AUTH_ERROR,
PLAYER_ME_REQUEST,
PLAYER_ME_SUCCESS,
TRANSFER_IN,
TRANSFER_OUT,
TRANSFER_SUCCESS,
WALLET_BALANCE_RESPONSE,
WALLET_CREDIT_REQUEST,
WALLET_DEBIT_REQUEST,
WALLET_FAIL,
WALLET_SUCCESS,
} from "@/modules/docs/integration/integration-doc-data";
import { useIntegrationDoc } from "@/modules/docs/integration/use-integration-doc";
function DocPage({ children }: { children: React.ReactNode }): React.ReactElement {
return <div className="space-y-8">{children}</div>;
}
export function OverviewDocScreen(): React.ReactElement {
const { p, rows, list, header } = useIntegrationDoc("overview");
return (
<DocPage>
<DocPageHeader title={p("title")} description={p("description")} />
<DocSection title={p("roles")}>
<DocTable compact headers={header("component")} rows={rows("matrix")} />
</DocSection>
<DocSection title={p("flow")}>
<DocOrderedList items={list("flowItems")} />
</DocSection>
<DocSection title={p("e2eSequence")}>
<DocTable compact headers={header("sequence")} rows={rows("e2eRows")} />
</DocSection>
<DocSection title={p("conventions")}>
<DocTable compact headers={header("convention")} rows={rows("conventionRows")} />
</DocSection>
<DocSection title={p("readingOrder")}>
<DocList items={list("readingItems")} />
</DocSection>
</DocPage>
);
}
export function QuickstartDocScreen(): React.ReactElement {
const { p, rows, list, header } = useIntegrationDoc("quickstart");
return (
<DocPage>
<DocPageHeader title={p("title")} description={p("description")} />
<DocSection title={p("prereq")}>
<DocList items={list("prereqItems")} />
</DocSection>
<DocSection title={p("steps")}>
<DocOrderedList items={list("stepItems")} />
</DocSection>
<DocSection title={p("testAccounts")}>
<DocTable compact headers={header("account")} rows={rows("accountRows")} />
</DocSection>
<DocSection title={p("reference")}>
<DocList items={list("referenceItems")} />
</DocSection>
<DocSection title={p("acceptance")}>
<DocOrderedList items={list("acceptanceItems")} />
<DocCode language="bash">{ACCEPTANCE_PLAYER_ME}</DocCode>
<DocCode language="bash">{ACCEPTANCE_WALLET_DEBIT}</DocCode>
</DocSection>
<DocNote>{p("note")}</DocNote>
</DocPage>
);
}
export function FundamentalsDocScreen(): React.ReactElement {
const { p, rows, header } = useIntegrationDoc("fundamentals");
return (
<DocPage>
<DocPageHeader title={p("title")} />
<DocSection title={p("balances")}>
<DocTable compact headers={header("balance")} rows={rows("balanceRows")} />
</DocSection>
<DocSection title={p("calls")}>
<DocTable compact headers={header("call")} rows={rows("callRows")} />
</DocSection>
<DocNote>{p("note")}</DocNote>
</DocPage>
);
}
export function SetupDocScreen(): React.ReactElement {
const { p, rows, list, header } = useIntegrationDoc("setup");
return (
<DocPage>
<DocPageHeader title={p("title")} description={p("description")} />
<DocSection title={p("weProvide")}>
<DocTable compact headers={header("param")} rows={rows("receiveRows")} />
</DocSection>
<DocSection title={p("youProvide")}>
<DocTable compact headers={header("param")} rows={rows("provideRows")} />
</DocSection>
<DocSection title={p("defaultPaths")}>
<DocTable compact headers={header("methodPath")} rows={rows("pathRows")} />
</DocSection>
<DocSection title={p("envMapping")}>
<DocTable compact headers={header("envMap")} rows={rows("envMappingRows")} />
</DocSection>
<DocSection title={p("adminSop")}>
<DocOrderedList items={list("adminSopSteps")} />
<DocTable compact headers={header("adminField")} rows={rows("adminFieldRows")} />
</DocSection>
<DocSection title={p("network")}>
<DocList items={list("networkItems")} />
</DocSection>
<DocNote>{p("note")}</DocNote>
</DocPage>
);
}
export function SsoDocScreen(): React.ReactElement {
const { p, rows, header } = useIntegrationDoc("sso");
return (
<DocPage>
<DocPageHeader title={p("title")} description={p("description")} />
<DocSection title={p("claims")}>
<DocTable compact headers={header("claim")} rows={rows("claimRows")} />
<DocCode>{SSO_JWT_PAYLOAD_EXAMPLE}</DocCode>
</DocSection>
<DocSection title={p("sign")}>
<DocCode language="typescript">{SSO_JWT_SIGN_EXAMPLE}</DocCode>
</DocSection>
<DocSection title={p("entryA")}>
<DocCode>{SSO_ENTRY_URL}</DocCode>
</DocSection>
<DocSection title={p("entryB")}>
<DocTable compact headers={header("message")} rows={rows("messageRows")} />
<DocCode language="typescript">{SSO_POSTMESSAGE}</DocCode>
<DocNote>{p("iframeNote")}</DocNote>
</DocSection>
<DocNote>{p("noExchangeNote")}</DocNote>
<DocSection title={p("entryApi")}>
<DocEndpoint method="GET" path="/api/v1/player/me" />
<DocNote>{p("entryApiNote")}</DocNote>
<DocCode language="http">{PLAYER_ME_REQUEST}</DocCode>
<DocCode>{PLAYER_ME_SUCCESS}</DocCode>
</DocSection>
<DocSection title={p("publicApis")}>
<DocTable compact headers={header("methodPath")} rows={rows("publicApiRows")} />
</DocSection>
<DocNote>{p("h5ScopeNote")}</DocNote>
<DocSection title={p("partnerApis")}>
<DocTable compact headers={header("methodPath")} rows={rows("partnerApiRows")} />
<DocNote>{p("refreshNote")}</DocNote>
</DocSection>
<DocSection title={p("authResponse")}>
<DocCode language="http">{PLAYER_AUTH_ERROR}</DocCode>
</DocSection>
<DocSection title={p("errors")}>
<DocTable compact headers={header("code")} rows={rows("errorRows")} />
</DocSection>
</DocPage>
);
}
export function IframeDocScreen(): React.ReactElement {
const { p, rows, list, header } = useIntegrationDoc("iframe");
return (
<DocPage>
<DocPageHeader title={p("title")} description={p("description")} />
<DocSection title={p("sequence")}>
<DocOrderedList items={list("sequenceSteps")} />
</DocSection>
<DocSection title={p("envelope")}>
<DocNote>{p("envelopeNote")}</DocNote>
</DocSection>
<DocSection title={p("childMessages")}>
<DocTable compact headers={header("message")} rows={rows("childMessageRows")} />
<DocCode>{IFRAME_CHILD_READY}</DocCode>
</DocSection>
<DocSection title={p("parentMessages")}>
<DocTable compact headers={header("message")} rows={rows("parentMessageRows")} />
<DocCode>{IFRAME_PARENT_INIT}</DocCode>
</DocSection>
<DocSection title={p("targetOrigin")}>
<DocNote>{p("targetOriginNote")}</DocNote>
</DocSection>
<DocNote>{p("timingNote")}</DocNote>
</DocPage>
);
}
export function WalletDocScreen(): React.ReactElement {
const { p, rows, header } = useIntegrationDoc("wallet");
return (
<DocPage>
<DocPageHeader title={p("title")} description={p("description")} />
<DocSection title={p("balance")}>
<DocEndpoint method="GET" path="/wallet/balance" />
<DocTable compact headers={header("query")} rows={rows("queryRows")} />
<DocCode>{WALLET_BALANCE_RESPONSE}</DocCode>
</DocSection>
<DocSection title={p("debit")}>
<DocEndpoint method="POST" path="/wallet/debit-for-lottery" />
<DocTable compact headers={header("field")} rows={rows("fieldRows")} />
<DocCode language="http">{WALLET_DEBIT_REQUEST}</DocCode>
</DocSection>
<DocSection title={p("credit")}>
<DocEndpoint method="POST" path="/wallet/credit-from-lottery" />
<DocCode language="http">{WALLET_CREDIT_REQUEST}</DocCode>
<DocNote>{p("creditNote")}</DocNote>
</DocSection>
<DocSection title={p("httpContract")}>
<DocTable compact headers={header("contract")} rows={rows("httpContractRows")} />
</DocSection>
<DocSection title={p("response")}>
<div className="grid gap-3 lg:grid-cols-2">
<DocCode>{WALLET_SUCCESS}</DocCode>
<DocCode>{WALLET_FAIL}</DocCode>
</div>
</DocSection>
<DocSection title={p("httpErrors")}>
<DocTable compact headers={header("http")} rows={rows("httpErrorRows")} />
</DocSection>
<DocNote>{p("idempotentNote")}</DocNote>
</DocPage>
);
}
export function TransferDocScreen(): React.ReactElement {
const { p, rows, header } = useIntegrationDoc("transfer");
return (
<DocPage>
<DocPageHeader title={p("title")} description={p("description")} />
<DocNote>{p("outOfScopeNote")}</DocNote>
<DocSection title={p("requestFields")}>
<DocTable compact headers={header("field")} rows={rows("requestFieldRows")} />
</DocSection>
<DocSection title={p("transferIn")}>
<DocEndpoint method="POST" path="/api/v1/wallet/transfer-in" />
<DocNote>{p("inNote")}</DocNote>
<DocCode language="http">{TRANSFER_IN}</DocCode>
</DocSection>
<DocSection title={p("transferOut")}>
<DocEndpoint method="POST" path="/api/v1/wallet/transfer-out" />
<DocNote>{p("outNote")}</DocNote>
<DocCode language="http">{TRANSFER_OUT}</DocCode>
</DocSection>
<DocSection title={p("transferResponse")}>
<DocNote>{p("responseNote")}</DocNote>
<DocCode>{TRANSFER_SUCCESS}</DocCode>
</DocSection>
<DocSection title={p("errors")}>
<DocTable compact headers={header("code")} rows={rows("errorRows")} />
</DocSection>
</DocPage>
);
}
export function ErrorsDocScreen(): React.ReactElement {
const { p, rows, header } = useIntegrationDoc("errors");
return (
<DocPage>
<DocPageHeader title={p("title")} />
<DocSection title={p("sso")}>
<DocTable compact headers={header("code")} rows={rows("ssoRows")} />
</DocSection>
<DocSection title={p("lotteryWallet")}>
<DocTable compact headers={header("code")} rows={rows("lotteryRows")} />
</DocSection>
<DocSection title={p("gateway")}>
<DocTable compact headers={header("http")} rows={rows("gatewayRows")} />
</DocSection>
<DocNote>{p("idempotentNote")}</DocNote>
</DocPage>
);
}
export function GoLiveDocScreen(): React.ReactElement {
const { p, list } = useIntegrationDoc("golive");
return (
<DocPage>
<DocPageHeader title={p("title")} />
<DocSection title={p("checklist")}>
<DocList items={list("items")} />
</DocSection>
</DocPage>
);
}

View File

@@ -0,0 +1,27 @@
"use client";
import { useTranslation } from "react-i18next";
type DocPageKey =
| "overview"
| "quickstart"
| "fundamentals"
| "setup"
| "sso"
| "iframe"
| "wallet"
| "transfer"
| "errors"
| "golive";
export function useIntegrationDoc(page: DocPageKey) {
const { t } = useTranslation("integrationDocs");
return {
t,
p: (key: string) => t(`pages.${page}.${key}`),
rows: (key: string) => t(`pages.${page}.${key}`, { returnObjects: true }) as string[][],
list: (key: string) => t(`pages.${page}.${key}`, { returnObjects: true }) as string[],
header: (key: string) => t(`headers.${key}`, { returnObjects: true }) as string[],
};
}