```
**删除 import**:行 10 的 `getAccessToken, getUserProfilePlan, isEmailInPlan` 改为只保留 `getAccessToken`(如其他地方仍用)。
**删除 import**:行 14 的 `UserPlan` 类型 import。
### B.3 `src/app/auth/page.tsx` — 登录页改造
**整体替换**:原文件 453 行包含 OAuth、Magic Link、Apple Sign-In、社交登录按钮,全部删除。改为只保留邮箱密码表单。
**新文件内容**(约 100 行):
```tsx
'use client';
import { useState } from 'react';
import { useRouter } from 'next/navigation';
import { useAuth } from '@/context/AuthContext';
import { useThemeStore } from '@/store/themeStore';
import { useTranslation } from '@/hooks/useTranslation';
import { supabase } from '@/utils/supabase';
import { IoArrowBack } from 'react-icons/io5';
import { navigateToLibrary } from '@/utils/nav';
export default function AuthPage() {
const _ = useTranslation();
const router = useRouter();
const { login } = useAuth();
const { isDarkMode } = useThemeStore();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
setError('');
try {
const { data, error } = await supabase.auth.signInWithPassword({ email, password });
if (error) throw error;
if (data.session && data.user) {
login(data.session.access_token, data.session.user as never);
router.push('/library');
}
} catch (err) {
setError(err instanceof Error ? err.message : _('Login failed'));
} finally {
setLoading(false);
}
};
return (
{_('Sign in to Readest')}
{_('Contact the administrator if you need an account.')}
);
}
```
**删除 import**(原行 1-29):所有 OAuth、Apple Sign-In、Tauri 相关 import 全部删除。
### B.4 `src/app/auth/callback/page.tsx`
**整体保留**(仍处理 hash 中的 access_token / refresh_token)。无需改动 — supabase-js 已替换为本地实现,`handleAuthCallback` 调用 `supabase.auth.setSession` 仍可工作。
### B.5 `src/app/auth/update/page.tsx` 与 `src/app/auth/recovery/page.tsx`
**保留**(仍可调用 supabase.auth.updateUser,但本地实现返回 403)。或在登录页移除"忘记密码"链接(如有)。**最小化改动:保留文件,用户访问时显示错误提示即可**。
### B.6 `src/app/user/page.tsx`
**删除 import**(原行 1-35 中的 plan/checkout/IAP 相关):
```tsx
// 删除:
import { useAvailablePlans } from '@/hooks/useAvailablePlans';
import { getPlanDetails } from './utils/plan';
import { purchaseIAPProduct, restoreIAPPurchases } from '@/libs/payment/iap/client';
import { isPurchaseProduct } from '@/libs/payment/iap/utils';
import { createStripeCheckoutSession, redirectToStripeCheckout, createStripePortalSession, redirectToStripePortal, handleStripeCheckoutError } from '@/libs/payment/stripe/client';
import PlansComparison from './components/PlansComparison';
import Checkout from './components/Checkout';
```
**删除组件状态**(约行 50-80 中的 checkoutState、showEmbeddedCheckout 等)。
**删除 JSX**(约行 320-380 中的 `
` 与 `` 块)。
**保留**:`UserInfo`、`UsageStats`、`AccountActions`、`StorageManager`、`SharedLinksSection`、`SyncPassphraseSection`、`SyncCategoriesSection`。
### B.7 `src/hooks/useQuotaStats.ts`
**整体替换**(简化为返回无限配额):
```ts
import { useEffect, useState } from 'react';
import { useAuth } from '@/context/AuthContext';
import { QuotaType, UserPlan } from '@/types/quota';
import { useTranslation } from './useTranslation';
export const useQuotaStats = (briefName = false) => {
const _ = useTranslation();
const { token, user } = useAuth();
const [quotas, setQuotas] = useState([]);
const [userProfilePlan, setUserProfilePlan] = useState(undefined);
useEffect(() => {
if (!user || !token) return;
setUserProfilePlan('pro');
setQuotas([
{ name: briefName ? _('Storage') : _('Cloud Sync Storage'),
tooltip: _('Unlimited'), used: 0, total: 1, unit: '' },
{ name: briefName ? _('Translation') : _('Translation Characters'),
tooltip: _('Unlimited'), used: 0, total: 1, unit: '', resetAt: 0 },
]);
}, [token, user, _, briefName]);
return { quotas, userProfilePlan };
};
```
### B.8 `src/services/constants.ts`
**修改行 833-845**(DEFAULT_STORAGE_QUOTA / DEFAULT_DAILY_TRANSLATION_QUOTA):
```ts
// 改为:
export const DEFAULT_STORAGE_QUOTA: UserStorageQuota = {
free: Number.MAX_SAFE_INTEGER,
plus: Number.MAX_SAFE_INTEGER,
pro: Number.MAX_SAFE_INTEGER,
purchase: Number.MAX_SAFE_INTEGER,
};
export const DEFAULT_DAILY_TRANSLATION_QUOTA: UserDailyTranslationQuota = {
free: Number.MAX_SAFE_INTEGER,
plus: Number.MAX_SAFE_INTEGER,
pro: Number.MAX_SAFE_INTEGER,
purchase: Number.MAX_SAFE_INTEGER,
};
```
**保留**:`SHARE_MAX_PER_USER = 50`(原版即如此,非 Pro 限制,是反滥用上限,保持不变)。
### B.9 `src/components/Quota.tsx`
**无需改动**(接收 quotas 数组渲染,新数据 `used=0, total=1` 会显示 0% 进度条)。
### B.10 `src/context/AuthContext.tsx`
**整体保留**。`supabase.auth.refreshSession()` / `supabase.auth.onAuthStateChange()` / `supabase.auth.signOut()` 全部由新 `utils/supabase.ts` 的 LocalSupabaseClient 提供等价实现。
### B.11 `src/services/translators/providers/deepl.ts`
**删除行 5, 30-31**(plan 解析):
```ts
// 原:
import { getSubscriptionPlan, getTranslationQuota } from '@/utils/access';
...
let userPlan: UserPlan = 'free';
if (token) {
userPlan = getSubscriptionPlan(token);
headers['Authorization'] = `Bearer ${token}`;
}
// 改为:
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
```
**删除行 47-50**(quota 计算):
```ts
// 原:
const quota = getTranslationQuota(userPlan);
// 删除
```
### B.12 `src/hooks/useTranslator.ts`
**删除行 149 附近的"Upgrade your plan"提示**(改为通用错误):
```ts
// 原:
'Daily translation quota reached. Upgrade your plan to continue using AI translations.',
// 改为:
'Translation failed. Please try again later.',
```
### B.13 `src/app/auth/page.tsx` 中行 175 的 `getUserProfilePlan` 调用
由于 B.3 整体替换了登录页,此行已不存在。
---
## C. 完全保留的文件(无改动)
- `src/pages/api/sync.ts`(已替换为本地实现,逻辑等价)
- `src/pages/api/sync/{replicas,replica-keys}.ts`
- `src/pages/api/storage/*`(已替换)
- `src/pages/api/send/*`(已替换)
- `src/pages/api/kosync.ts`(透传,无改动)
- `src/pages/api/deepl/translate.ts`(已替换)
- `src/app/api/share/*`(已替换)
- `src/app/api/{ai,tts,metadata,opds,hardcover}/*`(透传,仅 validateUserAndToken 替换)
- `src/app/api/auth/[...path]/route.ts`(新增,Supabase Auth 兼容层)
- `src/utils/{db,localAuth,localStorage,crdt,supabase,access,object}.ts`(新增或替换)
- `src/libs/{sync,shareServer,replicaSyncServer,replicaSchemas}.ts`(保留)
- `src/services/sync/*`(保留,同步客户端逻辑零改动)
- `src/libs/share.ts`(客户端 share API,零改动)
- `src/libs/replicaSyncClient.ts`(零改动)
- `src/components/*`(除 SettingsMenu 与 SendToReadestForm 外,全部保留)
- `src/app/{library,reader,s,o,send,opds,updater,offline,runtime-config.js}/*`(保留)
- `src/middleware.ts`(保留 CORS/COEP)
- `next.config.mjs`(保留)
---
## D. 验证清单
执行完所有改动后,按以下清单验证:
1. `pnpm install && pnpm build-web` 构建无错误
2. 启动后访问 `/auth`,仅显示邮箱密码表单,无社交登录按钮
3. 用 ADMIN_EMAIL/ADMIN_PASSWORD 登录成功
4. 进入 `/library`,设置菜单无"Upgrade to Readest Premium"
5. 进入 `/user`,无方案对比卡片,无 Checkout 组件
6. 上传一本书 → 进入阅读器 → 修改进度 → 刷新页面 → 进度保留
7. 创建分享链接 → 在另一个浏览器打开 → 能看到分享落地页
8. 翻译一段文字 → 不报配额错误
9. Send to Readest 面板无"View plans"升级卡片
10. 同步:设备 A 改笔记 → 设备 B 拉取 → 笔记同步成功