remove wallet functionality
This commit is contained in:
@@ -1,9 +1,6 @@
|
||||
# API origin. Leave empty for same-origin/local Vite proxy.
|
||||
VITE_API_URL=
|
||||
|
||||
# Reown / WalletConnect project id. Required for WalletConnect QR/mobile login.
|
||||
VITE_WALLETCONNECT_PROJECT_ID=
|
||||
|
||||
# Public production deploy disables admin routes.
|
||||
VITE_DISABLE_ADMIN=false
|
||||
|
||||
|
||||
@@ -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.
|
||||
- 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`.
|
||||
|
||||
## Branch rules
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
FROM node:20-alpine AS build
|
||||
WORKDIR /app
|
||||
ARG VITE_WALLETCONNECT_PROJECT_ID=
|
||||
ARG VITE_API_URL=
|
||||
ARG VITE_ADMIN_UI_PREFIX=
|
||||
ARG VITE_ADMIN_ONLY=
|
||||
ARG VITE_DISABLE_ADMIN=
|
||||
ENV VITE_WALLETCONNECT_PROJECT_ID=$VITE_WALLETCONNECT_PROJECT_ID
|
||||
ENV VITE_API_URL=$VITE_API_URL
|
||||
ENV VITE_ADMIN_UI_PREFIX=$VITE_ADMIN_UI_PREFIX
|
||||
ENV VITE_ADMIN_ONLY=$VITE_ADMIN_ONLY
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 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
|
||||
|
||||
@@ -8,7 +8,6 @@ React + Vite frontend for the ARK Library / ARK database site. The app serves pu
|
||||
- Vite 5
|
||||
- React Router
|
||||
- Tailwind CSS
|
||||
- RainbowKit / Wagmi / Viem for wallet connection
|
||||
- Gitea Actions deploy workflow on `main`
|
||||
|
||||
## Quick start
|
||||
@@ -54,7 +53,6 @@ Create a local `.env` only when needed. Do not commit secrets. See `.env.example
|
||||
| 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_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_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`. |
|
||||
|
||||
7847
package-lock.json
generated
7847
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -13,14 +13,10 @@
|
||||
"test:watch": "vitest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@rainbow-me/rainbowkit": "^2.2.11",
|
||||
"@tanstack/react-query": "^5.100.9",
|
||||
"lucide-react": "^0.460.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-router-dom": "^6.28.0",
|
||||
"viem": "^2.48.11",
|
||||
"wagmi": "^2.19.5"
|
||||
"react-router-dom": "^6.28.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
|
||||
15
src/App.tsx
15
src/App.tsx
@@ -1,4 +1,3 @@
|
||||
import { lazy, Suspense } from "react";
|
||||
import { BrowserRouter, Navigate, Route, Routes } from "react-router-dom";
|
||||
import { I18nProvider } from "./i18n";
|
||||
import { PublicLayout } from "./layouts/PublicLayout";
|
||||
@@ -14,12 +13,6 @@ import { AdminRouterModeProvider } from "./adminRouterMode";
|
||||
import { ImageLightboxProvider } from "./components/messageStream/overlays/ImageLightbox";
|
||||
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";
|
||||
|
||||
export default function App() {
|
||||
@@ -36,14 +29,6 @@ export default function App() {
|
||||
<Route path="/category/:slug" element={<CategoryPage />} />
|
||||
<Route path="/search" element={<SearchPage />} />
|
||||
<Route path="/resource/:id" element={<PostRedirect />} />
|
||||
<Route
|
||||
path="/wallet"
|
||||
element={
|
||||
<Suspense fallback={null}>
|
||||
<WalletPage />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route path="/about" element={<AboutPage />} />
|
||||
</Route>
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
33
src/i18n.tsx
33
src/i18n.tsx
@@ -33,7 +33,6 @@ const zhDict: Dict = {
|
||||
preview: "预览",
|
||||
download: "下载",
|
||||
share: "分享",
|
||||
profile: "个人中心",
|
||||
langLabel: "语言",
|
||||
admin: "后台",
|
||||
login: "登录",
|
||||
@@ -67,21 +66,6 @@ const zhDict: Dict = {
|
||||
total: "总资料",
|
||||
views: "浏览",
|
||||
downloads: "下载",
|
||||
wallet: "钱包",
|
||||
walletPageTitle: "钱包登录",
|
||||
walletPageIntro:
|
||||
"连接 Web3 钱包以使用会员相关功能。采用标准签名登录,不发送交易、不消耗 gas。",
|
||||
walletStepExtension:
|
||||
"电脑已安装浏览器扩展钱包(如 MetaMask)时,可直接连接。",
|
||||
walletStepQR:
|
||||
"电脑未安装钱包时:在连接窗口选择 WalletConnect,用手机钱包扫描 QR Code。",
|
||||
walletStepSign: "连接成功后,点击「签署登录」并在钱包内签名即可完成验证。",
|
||||
signInWallet: "签署登录",
|
||||
walletSignedIn: "已验证登录",
|
||||
walletLogout: "退出钱包",
|
||||
walletMissingProjectId:
|
||||
"请配置 VITE_WALLETCONNECT_PROJECT_ID(Reown Cloud),否则无法使用 WalletConnect/扫码。",
|
||||
walletSetupNeeded: "钱包扫码未启用(请在服务器配置环境变量)",
|
||||
lang_zh_CN: "中文",
|
||||
lang_en: "English",
|
||||
lang_ja: "日本語",
|
||||
@@ -158,7 +142,6 @@ const enDict: Dict = {
|
||||
preview: "Preview",
|
||||
download: "Download",
|
||||
share: "Share",
|
||||
profile: "Profile",
|
||||
langLabel: "Language",
|
||||
admin: "Admin",
|
||||
login: "Sign in",
|
||||
@@ -192,22 +175,6 @@ const enDict: Dict = {
|
||||
total: "Total items",
|
||||
views: "Views",
|
||||
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_en: "English",
|
||||
lang_ja: "Japanese",
|
||||
|
||||
@@ -20,7 +20,6 @@ type PublicNavWhich =
|
||||
| "browseLatest"
|
||||
| "browseRecommended"
|
||||
| "browsePopular"
|
||||
| "wallet"
|
||||
| "about";
|
||||
|
||||
function navIsActive(
|
||||
@@ -43,8 +42,6 @@ function navIsActive(
|
||||
return pathname === "/browse" && sp.get("sort") === "recommended";
|
||||
case "browsePopular":
|
||||
return pathname === "/browse" && sp.get("sort") === "popular";
|
||||
case "wallet":
|
||||
return pathname === "/wallet";
|
||||
case "about":
|
||||
return pathname === "/about";
|
||||
default:
|
||||
@@ -240,13 +237,6 @@ export function PublicLayout() {
|
||||
>
|
||||
{t("popular")}
|
||||
</Link>
|
||||
<Link
|
||||
to="/wallet"
|
||||
className={navClassName(na("wallet"))}
|
||||
aria-current={na("wallet") ? "page" : undefined}
|
||||
>
|
||||
{t("wallet")}
|
||||
</Link>
|
||||
</nav>
|
||||
|
||||
<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")}
|
||||
</Link>
|
||||
<Link
|
||||
to="/wallet"
|
||||
className={navClassName(na("wallet"))}
|
||||
aria-current={na("wallet") ? "page" : undefined}
|
||||
onClick={() => setOpen(false)}
|
||||
>
|
||||
{t("wallet")}
|
||||
</Link>
|
||||
<Link
|
||||
to="/about"
|
||||
className={navClassName(na("about"))}
|
||||
@@ -376,12 +358,6 @@ export function PublicLayout() {
|
||||
>
|
||||
{t("footerAbout")}
|
||||
</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" ? (
|
||||
<Link
|
||||
to={`${adminUiPrefix}/login`}
|
||||
@@ -443,7 +419,7 @@ function BottomNavIcon({
|
||||
}: {
|
||||
to: string;
|
||||
label: string;
|
||||
icon: "home" | "document" | "heart" | "profile" | "update";
|
||||
icon: "home" | "document" | "heart" | "update";
|
||||
active: boolean;
|
||||
}) {
|
||||
const src = active
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import "./index.css";
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
const adminOnly = import.meta.env.VITE_ADMIN_ONLY === "true";
|
||||
|
||||
void (async () => {
|
||||
@@ -23,9 +20,7 @@ void (async () => {
|
||||
const { default: App } = await import("./App");
|
||||
ReactDOM.createRoot(root).render(
|
||||
<React.StrictMode>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<App />
|
||||
</QueryClientProvider>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
);
|
||||
})();
|
||||
|
||||
@@ -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
1
src/vite-env.d.ts
vendored
@@ -3,7 +3,6 @@
|
||||
interface ImportMetaEnv {
|
||||
readonly VITE_API_URL: string;
|
||||
readonly VITE_API_PREFIX?: string;
|
||||
readonly VITE_WALLETCONNECT_PROJECT_ID: 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. */
|
||||
readonly VITE_ADMIN_ONLY?: string;
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user