remove wallet functionality

This commit is contained in:
TerryM
2026-05-27 10:40:02 +08:00
parent 1f89363b6d
commit 7546faf15e
15 changed files with 23 additions and 8125 deletions

View File

@@ -1,9 +1,6 @@
# API origin. Leave empty for same-origin/local Vite proxy. # API origin. Leave empty for same-origin/local Vite proxy.
VITE_API_URL= VITE_API_URL=
# Reown / WalletConnect project id. Required for WalletConnect QR/mobile login.
VITE_WALLETCONNECT_PROJECT_ID=
# Public production deploy disables admin routes. # Public production deploy disables admin routes.
VITE_DISABLE_ADMIN=false VITE_DISABLE_ADMIN=false

View File

@@ -6,7 +6,7 @@ This file is the first-stop context for AI coding agents working in this repo.
- Project: Arkie Library Frontend / ARK database web UI. - Project: Arkie Library Frontend / ARK database web UI.
- Package name: `ark-database-web`. - Package name: `ark-database-web`.
- Stack: React 18, TypeScript, Vite, Tailwind CSS, React Router, RainbowKit/Wagmi. - Stack: React 18, TypeScript, Vite, Tailwind CSS, React Router.
- Backend API is expected at `/api`; uploaded assets under `/uploads`. - Backend API is expected at `/api`; uploaded assets under `/uploads`.
## Branch rules ## Branch rules

View File

@@ -1,11 +1,9 @@
FROM node:20-alpine AS build FROM node:20-alpine AS build
WORKDIR /app WORKDIR /app
ARG VITE_WALLETCONNECT_PROJECT_ID=
ARG VITE_API_URL= ARG VITE_API_URL=
ARG VITE_ADMIN_UI_PREFIX= ARG VITE_ADMIN_UI_PREFIX=
ARG VITE_ADMIN_ONLY= ARG VITE_ADMIN_ONLY=
ARG VITE_DISABLE_ADMIN= ARG VITE_DISABLE_ADMIN=
ENV VITE_WALLETCONNECT_PROJECT_ID=$VITE_WALLETCONNECT_PROJECT_ID
ENV VITE_API_URL=$VITE_API_URL ENV VITE_API_URL=$VITE_API_URL
ENV VITE_ADMIN_UI_PREFIX=$VITE_ADMIN_UI_PREFIX ENV VITE_ADMIN_UI_PREFIX=$VITE_ADMIN_UI_PREFIX
ENV VITE_ADMIN_ONLY=$VITE_ADMIN_ONLY ENV VITE_ADMIN_ONLY=$VITE_ADMIN_ONLY

View File

@@ -1,6 +1,6 @@
# Arkie Library Frontend # Arkie Library Frontend
React + Vite frontend for the ARK Library / ARK database site. The app serves public resource browsing, search, favorites, wallet login UI, and an optional admin UI for resource management. React + Vite frontend for the ARK Library / ARK database site. The app serves public resource browsing, search, favorites, and an optional admin UI for resource management.
## Tech stack ## Tech stack
@@ -8,7 +8,6 @@ React + Vite frontend for the ARK Library / ARK database site. The app serves pu
- Vite 5 - Vite 5
- React Router - React Router
- Tailwind CSS - Tailwind CSS
- RainbowKit / Wagmi / Viem for wallet connection
- Gitea Actions deploy workflow on `main` - Gitea Actions deploy workflow on `main`
## Quick start ## Quick start
@@ -54,7 +53,6 @@ Create a local `.env` only when needed. Do not commit secrets. See `.env.example
| Variable | Purpose | | Variable | Purpose |
| ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `VITE_API_URL` | API/upload origin. Empty means same-origin and Vite dev proxy handles local `/api` and `/uploads`. Production deploy currently uses `https://api.ark-library.com`. | | `VITE_API_URL` | API/upload origin. Empty means same-origin and Vite dev proxy handles local `/api` and `/uploads`. Production deploy currently uses `https://api.ark-library.com`. |
| `VITE_WALLETCONNECT_PROJECT_ID` | Reown / WalletConnect project id. Needed for QR/mobile wallet connection. |
| `VITE_DISABLE_ADMIN` | When set to `"true"`, public build redirects admin routes away. Production public deploy sets this to `"true"`. | | `VITE_DISABLE_ADMIN` | When set to `"true"`, public build redirects admin routes away. Production public deploy sets this to `"true"`. |
| `VITE_ADMIN_ONLY` | When set to `"true"`, builds the admin-only app entry instead of the public app. | | `VITE_ADMIN_ONLY` | When set to `"true"`, builds the admin-only app entry instead of the public app. |
| `VITE_ADMIN_UI_PREFIX` | Optional admin UI base path. If absent in admin-only mode, code uses the secret prefix from `src/adminPaths.ts`. | | `VITE_ADMIN_UI_PREFIX` | Optional admin UI base path. If absent in admin-only mode, code uses the secret prefix from `src/adminPaths.ts`. |

7847
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -13,14 +13,10 @@
"test:watch": "vitest" "test:watch": "vitest"
}, },
"dependencies": { "dependencies": {
"@rainbow-me/rainbowkit": "^2.2.11",
"@tanstack/react-query": "^5.100.9",
"lucide-react": "^0.460.0", "lucide-react": "^0.460.0",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-router-dom": "^6.28.0", "react-router-dom": "^6.28.0"
"viem": "^2.48.11",
"wagmi": "^2.19.5"
}, },
"devDependencies": { "devDependencies": {
"@testing-library/jest-dom": "^6.9.1", "@testing-library/jest-dom": "^6.9.1",

View File

@@ -1,4 +1,3 @@
import { lazy, Suspense } from "react";
import { BrowserRouter, Navigate, Route, Routes } from "react-router-dom"; import { BrowserRouter, Navigate, Route, Routes } from "react-router-dom";
import { I18nProvider } from "./i18n"; import { I18nProvider } from "./i18n";
import { PublicLayout } from "./layouts/PublicLayout"; import { PublicLayout } from "./layouts/PublicLayout";
@@ -14,12 +13,6 @@ import { AdminRouterModeProvider } from "./adminRouterMode";
import { ImageLightboxProvider } from "./components/messageStream/overlays/ImageLightbox"; import { ImageLightboxProvider } from "./components/messageStream/overlays/ImageLightbox";
import { VideoPlayerProvider } from "./components/messageStream/overlays/VideoPlayer"; import { VideoPlayerProvider } from "./components/messageStream/overlays/VideoPlayer";
const WalletPage = lazy(() =>
import("./pages/Wallet").then((module) => ({
default: module.WalletPage,
})),
);
const adminEnabled = import.meta.env.VITE_DISABLE_ADMIN !== "true"; const adminEnabled = import.meta.env.VITE_DISABLE_ADMIN !== "true";
export default function App() { export default function App() {
@@ -36,14 +29,6 @@ export default function App() {
<Route path="/category/:slug" element={<CategoryPage />} /> <Route path="/category/:slug" element={<CategoryPage />} />
<Route path="/search" element={<SearchPage />} /> <Route path="/search" element={<SearchPage />} />
<Route path="/resource/:id" element={<PostRedirect />} /> <Route path="/resource/:id" element={<PostRedirect />} />
<Route
path="/wallet"
element={
<Suspense fallback={null}>
<WalletPage />
</Suspense>
}
/>
<Route path="/about" element={<AboutPage />} /> <Route path="/about" element={<AboutPage />} />
</Route> </Route>

View File

@@ -1,127 +0,0 @@
import { ConnectButton } from "@rainbow-me/rainbowkit";
import { useAccount, useDisconnect, useSignMessage } from "wagmi";
import { useCallback, useEffect, useState } from "react";
import { postJSON, apiBase } from "../api";
import {
clearWalletToken,
getWalletToken,
setWalletToken,
} from "../walletToken";
import { useI18n } from "../i18n";
export function WalletLoginControls() {
const { t } = useI18n();
const { address, isConnected } = useAccount();
const { disconnectAsync } = useDisconnect();
const { signMessageAsync, isPending: signing } = useSignMessage();
const [busy, setBusy] = useState(false);
const [err, setErr] = useState<string | null>(null);
const [sessionOk, setSessionOk] = useState(false);
const checkSession = useCallback(async () => {
const tok = getWalletToken();
if (!tok || !address) {
setSessionOk(false);
return;
}
try {
const r = await fetch(`${apiBase}/api/auth/wallet/me`, {
headers: { Authorization: `Bearer ${tok}` },
});
if (!r.ok) {
clearWalletToken();
setSessionOk(false);
return;
}
const j = (await r.json()) as { wallet: string };
setSessionOk(j.wallet?.toLowerCase() === address.toLowerCase());
} catch {
setSessionOk(false);
}
}, [address]);
useEffect(() => {
void checkSession();
}, [checkSession]);
useEffect(() => {
if (!address) clearWalletToken();
}, [address]);
const signIn = async () => {
if (!address) return;
setErr(null);
setBusy(true);
try {
const nonceRes = await postJSON<{ message: string }>(
"/api/auth/wallet/nonce",
{ address },
);
const sig = await signMessageAsync({ message: nonceRes.message });
const out = await postJSON<{ token: string }>("/api/auth/wallet/verify", {
address,
message: nonceRes.message,
signature: sig,
});
setWalletToken(out.token);
setSessionOk(true);
} catch (e) {
setErr(String(e));
} finally {
setBusy(false);
}
};
const signOut = async () => {
clearWalletToken();
setSessionOk(false);
await disconnectAsync();
};
if (!import.meta.env.VITE_WALLETCONNECT_PROJECT_ID) {
return (
<span
className="text-xs text-amber-500/90 max-w-[220px] text-right leading-tight"
title={t("walletMissingProjectId")}
>
{t("walletSetupNeeded")}
</span>
);
}
return (
<div className="flex flex-col items-end gap-1 min-w-[200px]">
<div className="flex flex-wrap items-center justify-end gap-2">
<ConnectButton chainStatus="icon" showBalance={false} />
{isConnected && address ? (
sessionOk ? (
<span className="text-xs text-ark-gold2 whitespace-nowrap">
{t("walletSignedIn")}
</span>
) : (
<button
type="button"
disabled={busy || signing}
onClick={() => void signIn()}
className="rounded-lg border border-ark-gold bg-ark-gold/10 px-3 py-1.5 text-xs font-medium text-ark-gold2 hover:bg-ark-gold/20 disabled:opacity-50"
>
{busy || signing ? "…" : t("signInWallet")}
</button>
)
) : null}
{isConnected && sessionOk ? (
<button
type="button"
onClick={() => void signOut()}
className="text-xs text-neutral-500 hover:text-white"
>
{t("walletLogout")}
</button>
) : null}
</div>
{err ? (
<p className="text-xs text-red-400 max-w-xs text-right">{err}</p>
) : null}
</div>
);
}

View File

@@ -33,7 +33,6 @@ const zhDict: Dict = {
preview: "预览", preview: "预览",
download: "下载", download: "下载",
share: "分享", share: "分享",
profile: "个人中心",
langLabel: "语言", langLabel: "语言",
admin: "后台", admin: "后台",
login: "登录", login: "登录",
@@ -67,21 +66,6 @@ const zhDict: Dict = {
total: "总资料", total: "总资料",
views: "浏览", views: "浏览",
downloads: "下载", downloads: "下载",
wallet: "钱包",
walletPageTitle: "钱包登录",
walletPageIntro:
"连接 Web3 钱包以使用会员相关功能。采用标准签名登录,不发送交易、不消耗 gas。",
walletStepExtension:
"电脑已安装浏览器扩展钱包(如 MetaMask可直接连接。",
walletStepQR:
"电脑未安装钱包时:在连接窗口选择 WalletConnect用手机钱包扫描 QR Code。",
walletStepSign: "连接成功后,点击「签署登录」并在钱包内签名即可完成验证。",
signInWallet: "签署登录",
walletSignedIn: "已验证登录",
walletLogout: "退出钱包",
walletMissingProjectId:
"请配置 VITE_WALLETCONNECT_PROJECT_IDReown Cloud否则无法使用 WalletConnect/扫码。",
walletSetupNeeded: "钱包扫码未启用(请在服务器配置环境变量)",
lang_zh_CN: "中文", lang_zh_CN: "中文",
lang_en: "English", lang_en: "English",
lang_ja: "日本語", lang_ja: "日本語",
@@ -158,7 +142,6 @@ const enDict: Dict = {
preview: "Preview", preview: "Preview",
download: "Download", download: "Download",
share: "Share", share: "Share",
profile: "Profile",
langLabel: "Language", langLabel: "Language",
admin: "Admin", admin: "Admin",
login: "Sign in", login: "Sign in",
@@ -192,22 +175,6 @@ const enDict: Dict = {
total: "Total items", total: "Total items",
views: "Views", views: "Views",
downloads: "Downloads", downloads: "Downloads",
wallet: "Wallet",
walletPageTitle: "Wallet sign-in",
walletPageIntro:
"Connect a Web3 wallet for member features. This uses a standard signed message — no transaction and no gas.",
walletStepExtension:
"On desktop with a browser extension (e.g. MetaMask), connect directly.",
walletStepQR:
"On desktop without an extension: choose WalletConnect in the modal and scan the QR code with your mobile wallet.",
walletStepSign:
'After connecting, tap "Sign in" and approve the message in your wallet to verify.',
signInWallet: "Sign in",
walletSignedIn: "Signed in",
walletLogout: "Disconnect",
walletMissingProjectId:
"Set VITE_WALLETCONNECT_PROJECT_ID (free on Reown Cloud). Required for WalletConnect / QR login.",
walletSetupNeeded: "Wallet QR login disabled (set env on server)",
lang_zh_CN: "Chinese", lang_zh_CN: "Chinese",
lang_en: "English", lang_en: "English",
lang_ja: "Japanese", lang_ja: "Japanese",

View File

@@ -20,7 +20,6 @@ type PublicNavWhich =
| "browseLatest" | "browseLatest"
| "browseRecommended" | "browseRecommended"
| "browsePopular" | "browsePopular"
| "wallet"
| "about"; | "about";
function navIsActive( function navIsActive(
@@ -43,8 +42,6 @@ function navIsActive(
return pathname === "/browse" && sp.get("sort") === "recommended"; return pathname === "/browse" && sp.get("sort") === "recommended";
case "browsePopular": case "browsePopular":
return pathname === "/browse" && sp.get("sort") === "popular"; return pathname === "/browse" && sp.get("sort") === "popular";
case "wallet":
return pathname === "/wallet";
case "about": case "about":
return pathname === "/about"; return pathname === "/about";
default: default:
@@ -240,13 +237,6 @@ export function PublicLayout() {
> >
{t("popular")} {t("popular")}
</Link> </Link>
<Link
to="/wallet"
className={navClassName(na("wallet"))}
aria-current={na("wallet") ? "page" : undefined}
>
{t("wallet")}
</Link>
</nav> </nav>
<div className="flex min-w-0 flex-1 items-center justify-end gap-2 min-[1200px]:flex-none"> <div className="flex min-w-0 flex-1 items-center justify-end gap-2 min-[1200px]:flex-none">
@@ -344,14 +334,6 @@ export function PublicLayout() {
> >
{t("popular")} {t("popular")}
</Link> </Link>
<Link
to="/wallet"
className={navClassName(na("wallet"))}
aria-current={na("wallet") ? "page" : undefined}
onClick={() => setOpen(false)}
>
{t("wallet")}
</Link>
<Link <Link
to="/about" to="/about"
className={navClassName(na("about"))} className={navClassName(na("about"))}
@@ -376,12 +358,6 @@ export function PublicLayout() {
> >
{t("footerAbout")} {t("footerAbout")}
</Link> </Link>
<Link
to="/wallet"
className="rounded-sm outline-none hover:text-ark-gold2 focus-visible:ring-2 focus-visible:ring-ark-gold/80 focus-visible:ring-offset-2 focus-visible:ring-offset-ark-bg"
>
{t("profile")}
</Link>
{import.meta.env.VITE_DISABLE_ADMIN !== "true" ? ( {import.meta.env.VITE_DISABLE_ADMIN !== "true" ? (
<Link <Link
to={`${adminUiPrefix}/login`} to={`${adminUiPrefix}/login`}
@@ -443,7 +419,7 @@ function BottomNavIcon({
}: { }: {
to: string; to: string;
label: string; label: string;
icon: "home" | "document" | "heart" | "profile" | "update"; icon: "home" | "document" | "heart" | "update";
active: boolean; active: boolean;
}) { }) {
const src = active const src = active

View File

@@ -1,10 +1,7 @@
import React from "react"; import React from "react";
import ReactDOM from "react-dom/client"; import ReactDOM from "react-dom/client";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import "./index.css"; import "./index.css";
const queryClient = new QueryClient();
const adminOnly = import.meta.env.VITE_ADMIN_ONLY === "true"; const adminOnly = import.meta.env.VITE_ADMIN_ONLY === "true";
void (async () => { void (async () => {
@@ -23,9 +20,7 @@ void (async () => {
const { default: App } = await import("./App"); const { default: App } = await import("./App");
ReactDOM.createRoot(root).render( ReactDOM.createRoot(root).render(
<React.StrictMode> <React.StrictMode>
<QueryClientProvider client={queryClient}>
<App /> <App />
</QueryClientProvider>
</React.StrictMode>, </React.StrictMode>,
); );
})(); })();

View File

@@ -1,47 +0,0 @@
import { RainbowKitProvider, darkTheme } from "@rainbow-me/rainbowkit";
import { WagmiProvider } from "wagmi";
import "@rainbow-me/rainbowkit/styles.css";
import { WalletLoginControls } from "../../components/WalletLoginControls";
import { useI18n } from "../../i18n";
import { wagmiConfig } from "../../wagmiConfig";
export function WalletPage() {
const { t } = useI18n();
return (
<div className="mx-auto max-w-lg space-y-6">
<h1 className="text-2xl font-bold">{t("walletPageTitle")}</h1>
<p className="text-neutral-300 text-sm leading-relaxed">
{t("walletPageIntro")}
</p>
<ul className="text-sm text-neutral-400 space-y-2 list-disc pl-5">
<li>{t("walletStepExtension")}</li>
<li>{t("walletStepQR")}</li>
<li>{t("walletStepSign")}</li>
</ul>
<div className="rounded-2xl border border-ark-line bg-ark-panel p-6 space-y-4">
{import.meta.env.VITE_WALLETCONNECT_PROJECT_ID ? (
<WagmiProvider config={wagmiConfig}>
<RainbowKitProvider
theme={darkTheme({
accentColor: "#d4af37",
accentColorForeground: "#0a0a0a",
borderRadius: "medium",
})}
modalSize="wide"
>
<WalletLoginControls />
</RainbowKitProvider>
</WagmiProvider>
) : (
<p
className="text-sm text-amber-500/90 leading-relaxed"
title={t("walletMissingProjectId")}
>
{t("walletSetupNeeded")}
</p>
)}
</div>
</div>
);
}

1
src/vite-env.d.ts vendored
View File

@@ -3,7 +3,6 @@
interface ImportMetaEnv { interface ImportMetaEnv {
readonly VITE_API_URL: string; readonly VITE_API_URL: string;
readonly VITE_API_PREFIX?: string; readonly VITE_API_PREFIX?: string;
readonly VITE_WALLETCONNECT_PROJECT_ID: string;
readonly VITE_ADMIN_UI_PREFIX?: string; readonly VITE_ADMIN_UI_PREFIX?: string;
/** When `"true"`, bundle admin UI only (no public pages); use with `VITE_ADMIN_UI_PREFIX` or default secret prefix. */ /** When `"true"`, bundle admin UI only (no public pages); use with `VITE_ADMIN_UI_PREFIX` or default secret prefix. */
readonly VITE_ADMIN_ONLY?: string; readonly VITE_ADMIN_ONLY?: string;

View File

@@ -1,15 +0,0 @@
import { getDefaultConfig } from "@rainbow-me/rainbowkit";
import { arbitrum, bsc, mainnet, polygon, sepolia } from "wagmi/chains";
/**
* Get a free Project ID: https://cloud.reown.com (WalletConnect / Reown)
* Without it, WalletConnect (mobile / QR on desktop) will not work; browser extensions may still work in some setups.
*/
const projectId = import.meta.env.VITE_WALLETCONNECT_PROJECT_ID || "";
export const wagmiConfig = getDefaultConfig({
appName: "ARK Database",
projectId: projectId || "00000000000000000000000000000000",
chains: [mainnet, bsc, arbitrum, polygon, sepolia],
ssr: false,
});

View File

@@ -1,13 +0,0 @@
const KEY = "ark_wallet_token";
export function getWalletToken() {
return localStorage.getItem(KEY) || "";
}
export function setWalletToken(t: string) {
localStorage.setItem(KEY, t);
}
export function clearWalletToken() {
localStorage.removeItem(KEY);
}