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:
55
src/components/docs/doc-code.tsx
Normal file
55
src/components/docs/doc-code.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { highlightCode, type HighlightLang } from "@/lib/highlight-code";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
type DocCodeProps = {
|
||||
children: string;
|
||||
language?: HighlightLang;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export function DocCode({
|
||||
children,
|
||||
language = "json",
|
||||
className,
|
||||
}: DocCodeProps): React.ReactElement {
|
||||
const [html, setHtml] = useState<string | null>(null);
|
||||
const code = children.trimEnd();
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
|
||||
void highlightCode(code, language).then((result) => {
|
||||
if (!cancelled) {
|
||||
setHtml(result);
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [code, language]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"overflow-x-auto rounded-xl border border-border bg-[#f6f8fa]",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{html ? (
|
||||
<div
|
||||
className="[&_code]:font-mono [&_pre]:m-0 [&_pre]:overflow-x-auto [&_pre]:bg-transparent [&_pre]:p-4 [&_pre]:text-[13px] [&_pre]:leading-7"
|
||||
dangerouslySetInnerHTML={{ __html: html }}
|
||||
/>
|
||||
) : (
|
||||
<pre className="m-0 overflow-x-auto p-4 font-mono text-[13px] leading-7 text-foreground">
|
||||
{code}
|
||||
</pre>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
129
src/components/docs/doc-ui.tsx
Normal file
129
src/components/docs/doc-ui.tsx
Normal file
@@ -0,0 +1,129 @@
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export { DocCode } from "@/components/docs/doc-code";
|
||||
|
||||
export function DocPageHeader({
|
||||
title,
|
||||
description,
|
||||
}: {
|
||||
title: string;
|
||||
description?: string;
|
||||
}): React.ReactElement {
|
||||
return (
|
||||
<header className="border-b border-border pb-6">
|
||||
<h1 className="text-2xl font-semibold tracking-tight text-foreground">{title}</h1>
|
||||
{description ? (
|
||||
<p className="mt-2 max-w-3xl text-sm leading-6 text-muted-foreground">{description}</p>
|
||||
) : null}
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
export function DocSection({
|
||||
title,
|
||||
children,
|
||||
className,
|
||||
}: {
|
||||
title?: string;
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
}): React.ReactElement {
|
||||
return (
|
||||
<section className={cn("space-y-3", className)}>
|
||||
{title ? <h2 className="text-base font-semibold text-foreground">{title}</h2> : null}
|
||||
{children}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export function DocNote({ children }: { children: ReactNode }): React.ReactElement {
|
||||
return (
|
||||
<p className="border-l-2 border-border pl-3 text-sm leading-6 text-muted-foreground">{children}</p>
|
||||
);
|
||||
}
|
||||
|
||||
export function DocList({ items }: { items: readonly string[] }): React.ReactElement {
|
||||
return (
|
||||
<ul className="space-y-1 text-sm leading-6 text-muted-foreground">
|
||||
{items.map((item) => (
|
||||
<li key={item} className="flex gap-2">
|
||||
<span className="text-muted-foreground/50">·</span>
|
||||
<span>{item}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
export function DocOrderedList({ items }: { items: readonly string[] }): React.ReactElement {
|
||||
return (
|
||||
<ol className="list-decimal space-y-1 pl-5 text-sm leading-6 text-muted-foreground">
|
||||
{items.map((item) => (
|
||||
<li key={item}>{item}</li>
|
||||
))}
|
||||
</ol>
|
||||
);
|
||||
}
|
||||
|
||||
export function DocTable({
|
||||
headers,
|
||||
rows,
|
||||
compact,
|
||||
}: {
|
||||
headers: readonly string[];
|
||||
rows: readonly (readonly string[])[];
|
||||
compact?: boolean;
|
||||
}): React.ReactElement {
|
||||
return (
|
||||
<div className="overflow-x-auto rounded-lg border border-border">
|
||||
<table className="w-full min-w-[480px] border-collapse text-sm">
|
||||
<thead>
|
||||
<tr className="border-b border-border bg-muted/30">
|
||||
{headers.map((header) => (
|
||||
<th
|
||||
key={header}
|
||||
className="px-3 py-2 text-left text-[11px] font-medium uppercase tracking-wide text-muted-foreground"
|
||||
>
|
||||
{header}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{rows.map((row, rowIndex) => (
|
||||
<tr key={`${row[0]}-${rowIndex}`} className="border-b border-border/60 last:border-0">
|
||||
{row.map((cell, cellIndex) => (
|
||||
<td
|
||||
key={`${row[0]}-${cellIndex}`}
|
||||
className={cn(
|
||||
"px-3 align-top text-muted-foreground",
|
||||
compact ? "py-1.5 text-[13px] leading-5" : "py-2 text-[13px] leading-6",
|
||||
)}
|
||||
>
|
||||
{cell}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function DocEndpoint({ method, path }: { method: string; path: string }): React.ReactElement {
|
||||
return (
|
||||
<div className="inline-flex items-center gap-2 font-mono text-xs">
|
||||
<span className="rounded bg-primary/10 px-1.5 py-0.5 font-semibold text-primary">{method}</span>
|
||||
<span className="text-foreground">{path}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function DocInlineCode({ children }: { children: ReactNode }): React.ReactElement {
|
||||
return (
|
||||
<code className="rounded bg-muted px-1 py-0.5 font-mono text-[12px] text-foreground">{children}</code>
|
||||
);
|
||||
}
|
||||
53
src/components/docs/docs-shell.tsx
Normal file
53
src/components/docs/docs-shell.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { AdminLanguageSwitcher } from "@/components/admin/admin-language-switcher";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
type DocsShellProps = {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export function DocsShell({ children, className }: DocsShellProps): React.ReactElement {
|
||||
const { t } = useTranslation("integrationDocs");
|
||||
|
||||
return (
|
||||
<div className={cn("min-h-dvh bg-background text-foreground", className)}>
|
||||
<header className="sticky top-0 z-40 border-b border-border/80 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/80">
|
||||
<div className="mx-auto flex h-14 max-w-6xl items-center justify-between gap-4 px-4 sm:px-6 lg:px-8">
|
||||
<Link href="/docs/integration" className="truncate text-sm font-semibold tracking-tight">
|
||||
{t("shell.title")}
|
||||
</Link>
|
||||
<div className="flex items-center gap-2">
|
||||
<AdminLanguageSwitcher />
|
||||
<Link
|
||||
href="/admin/login"
|
||||
className="text-xs text-muted-foreground transition-colors hover:text-foreground"
|
||||
>
|
||||
{t("shell.admin")}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function DocsBody({
|
||||
sidebar,
|
||||
children,
|
||||
}: {
|
||||
sidebar?: React.ReactNode;
|
||||
children: React.ReactNode;
|
||||
}): React.ReactElement {
|
||||
return (
|
||||
<div className="mx-auto flex w-full max-w-6xl flex-col gap-6 px-4 py-6 sm:px-6 lg:flex-row lg:gap-8 lg:px-8 lg:py-8">
|
||||
{sidebar}
|
||||
<main className="min-w-0 flex-1 pb-12">{children}</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
62
src/components/docs/docs-sidebar.tsx
Normal file
62
src/components/docs/docs-sidebar.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import type { DocsNavGroup } from "@/lib/docs-nav";
|
||||
import { resolveDocsNavLabel } from "@/lib/docs-nav-labels";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export function DocsSidebar({ groups }: { groups: readonly DocsNavGroup[] }): React.ReactElement {
|
||||
const pathname = usePathname();
|
||||
const { t, i18n } = useTranslation("integrationDocs");
|
||||
|
||||
const label = (key: string): string => {
|
||||
const translated = t(key);
|
||||
if (translated !== key) {
|
||||
return translated;
|
||||
}
|
||||
return resolveDocsNavLabel(key, i18n.resolvedLanguage ?? i18n.language);
|
||||
};
|
||||
|
||||
return (
|
||||
<aside className="w-full shrink-0 lg:w-44">
|
||||
<div className="lg:sticky lg:top-14 lg:max-h-[calc(100dvh-4rem)] lg:overflow-y-auto lg:pr-2">
|
||||
<nav className="space-y-4">
|
||||
{groups.map((group) => (
|
||||
<div key={group.titleKey}>
|
||||
<div className="mb-1 px-1.5 text-[10px] font-medium uppercase tracking-wide text-muted-foreground/80">
|
||||
{label(group.titleKey)}
|
||||
</div>
|
||||
<ul className="space-y-px">
|
||||
{group.items.map((item) => {
|
||||
const active =
|
||||
pathname === item.href ||
|
||||
(item.href !== "/docs/integration" && pathname.startsWith(item.href)) ||
|
||||
(item.href === "/docs/integration" && pathname === "/docs/integration");
|
||||
|
||||
return (
|
||||
<li key={item.href}>
|
||||
<Link
|
||||
href={item.href}
|
||||
className={cn(
|
||||
"block rounded-md px-2 py-1.5 text-[13px] leading-5 transition-colors",
|
||||
active
|
||||
? "bg-primary/10 font-medium text-foreground"
|
||||
: "text-muted-foreground hover:bg-muted/40 hover:text-foreground",
|
||||
)}
|
||||
>
|
||||
{label(item.titleKey)}
|
||||
</Link>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</nav>
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user