Skip to content

Commit fb78f40

Browse files
authored
Merge pull request #18 from OasAIStudio/feature/OAuth
Feature/o auth
2 parents eac6161 + 294818f commit fb78f40

File tree

28 files changed

+1805
-711
lines changed

28 files changed

+1805
-711
lines changed
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# Login Modal Design
2+
3+
**Date:** 2026-02-28
4+
**Status:** Approved
5+
6+
## Problem
7+
8+
The Navbar currently shows two separate login buttons (Google + GitHub) side by side when the user is not logged in. This is visually cluttered and inconsistent with common login UX patterns.
9+
10+
## Solution
11+
12+
Replace the two inline login buttons with a single "Log In" button. Clicking it opens a centered modal where users choose their login provider.
13+
14+
## UI Design
15+
16+
### Navbar (unauthenticated state)
17+
18+
Before:
19+
```
20+
[ 中文 ] [ GitHub ] [ 🔵 Google ] [ ⚫ GitHub ]
21+
```
22+
23+
After:
24+
```
25+
[ 中文 ] [ GitHub ] [ Log In ]
26+
```
27+
28+
The "Log In" button uses the brand accent color (orange).
29+
30+
### Modal
31+
32+
```
33+
┌─────────────────────────────────────┐
34+
│ Welcome to MoltMarket [×] │
35+
│ │
36+
│ Choose a login method │
37+
│ │
38+
│ ┌─────────────────────────────┐ │
39+
│ │ 🔵 Continue with Google │ │
40+
│ └─────────────────────────────┘ │
41+
│ │
42+
│ ┌─────────────────────────────┐ │
43+
│ │ ⚫ Continue with GitHub │ │
44+
│ └─────────────────────────────┘ │
45+
│ │
46+
└─────────────────────────────────────┘
47+
```
48+
49+
- Semi-transparent black overlay; clicking overlay closes modal
50+
- Centered card with rounded corners and light shadow
51+
- Close button (×) in top-right corner
52+
- Google button: border style (existing style)
53+
- GitHub button: dark fill style (existing style)
54+
55+
## Technical Plan
56+
57+
### Files to change
58+
59+
1. **`src/components/Navbar.tsx`**
60+
- Remove the two inline login `<a>` tags
61+
- Add a "Log In" `<button>` with `onClick` to open modal
62+
- Add `useState(false)` for modal visibility
63+
- Add modal JSX: overlay + card with Google and GitHub login links
64+
65+
2. **`messages/en.json`**
66+
- Add `Navbar.login`, `Navbar.loginTitle`, `Navbar.loginSubtitle`
67+
68+
3. **`messages/zh.json`**
69+
- Add corresponding Chinese translations
70+
71+
### No new files required
72+
73+
All changes are self-contained within the existing Navbar component and translation files.
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
# Login Modal Implementation Plan
2+
3+
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
4+
5+
**Goal:** Replace the two inline login buttons (Google + GitHub) in the Navbar with a single "Log In" button that opens a centered modal for the user to choose a login provider.
6+
7+
**Architecture:** All changes are self-contained in `Navbar.tsx` — add a `useState` boolean for modal visibility, replace the two `<a>` login tags with one `<button>`, and append a modal overlay + card as a portal-free JSX sibling inside the `<nav>`. Translation keys for the new copy live in `messages/en.json` and `messages/zh.json`.
8+
9+
**Tech Stack:** Next.js 14, TypeScript, Tailwind CSS, next-intl
10+
11+
---
12+
13+
### Task 1: Add translation keys
14+
15+
**Files:**
16+
- Modify: `messages/en.json`
17+
- Modify: `messages/zh.json`
18+
19+
**Step 1: Add keys to `messages/en.json`**
20+
21+
Inside the `"Navbar"` object, add three keys:
22+
23+
```json
24+
"login": "Log In",
25+
"loginTitle": "Welcome to MoltMarket",
26+
"loginSubtitle": "Choose a login method"
27+
```
28+
29+
After editing, the `"Navbar"` block should look like:
30+
```json
31+
"Navbar": {
32+
"home": "Home",
33+
"overview": "Overview",
34+
"tokenMarket": "Token Recycling Market",
35+
"logout": "Logout",
36+
"startEarning": "Start Earning",
37+
"login": "Log In",
38+
"loginTitle": "Welcome to MoltMarket",
39+
"loginSubtitle": "Choose a login method"
40+
}
41+
```
42+
43+
**Step 2: Add keys to `messages/zh.json`**
44+
45+
Inside the `"Navbar"` object, add the same three keys in Chinese:
46+
47+
```json
48+
"login": "登录",
49+
"loginTitle": "欢迎来到 MoltMarket",
50+
"loginSubtitle": "选择登录方式"
51+
```
52+
53+
**Step 3: Commit**
54+
55+
```bash
56+
git add messages/en.json messages/zh.json
57+
git commit -m "feat: add login modal translation keys"
58+
```
59+
60+
---
61+
62+
### Task 2: Rewrite the unauthenticated login section in Navbar
63+
64+
**Files:**
65+
- Modify: `src/components/Navbar.tsx`
66+
67+
**Step 1: Add `useState` import and modal state**
68+
69+
At the top of the component function body (after the existing `const pathname = usePathname();` line), add:
70+
71+
```tsx
72+
const [loginOpen, setLoginOpen] = useState(false);
73+
```
74+
75+
Also add `useState` to the React import at the top of the file:
76+
77+
```tsx
78+
import { useState } from "react";
79+
```
80+
81+
**Step 2: Replace the unauthenticated login JSX**
82+
83+
Find the `else` branch of `{userName ? (...) : (...)}` — it currently renders a `<div className="flex items-center gap-2">` containing the Google `<a>` and GitHub `<a>` tags.
84+
85+
Replace that entire `<div>` (lines 109–148 in the original file) with a single button:
86+
87+
```tsx
88+
<button
89+
onClick={() => setLoginOpen(true)}
90+
className="font-inter text-[15px] font-semibold text-white rounded-lg px-4 py-2 bg-[var(--accent)] hover:opacity-90 transition-opacity cursor-pointer"
91+
>
92+
{t("login")}
93+
</button>
94+
```
95+
96+
**Step 3: Add the modal JSX**
97+
98+
Immediately before the closing `</nav>` tag, add:
99+
100+
```tsx
101+
{loginOpen && (
102+
<>
103+
{/* Overlay */}
104+
<div
105+
className="fixed inset-0 z-[100] bg-black/50 backdrop-blur-sm"
106+
onClick={() => setLoginOpen(false)}
107+
/>
108+
{/* Modal card */}
109+
<div className="fixed inset-0 z-[101] flex items-center justify-center pointer-events-none">
110+
<div className="pointer-events-auto w-[360px] bg-[var(--bg-primary)] rounded-2xl shadow-2xl p-8 flex flex-col gap-6">
111+
{/* Header */}
112+
<div className="flex items-start justify-between">
113+
<div>
114+
<h2 className="font-inter text-[20px] font-bold text-[var(--text-primary)]">
115+
{t("loginTitle")}
116+
</h2>
117+
<p className="font-inter text-[14px] text-[var(--text-muted)] mt-1">
118+
{t("loginSubtitle")}
119+
</p>
120+
</div>
121+
<button
122+
onClick={() => setLoginOpen(false)}
123+
className="text-[var(--text-muted)] hover:text-[var(--text-primary)] transition-colors text-[20px] leading-none cursor-pointer"
124+
>
125+
×
126+
</button>
127+
</div>
128+
129+
{/* Login options */}
130+
<div className="flex flex-col gap-3">
131+
{/* Google */}
132+
<a
133+
href="/api/auth/login?provider=google"
134+
className="flex items-center gap-3 font-inter text-[15px] font-medium text-[var(--text-primary)] rounded-xl px-4 py-3 border border-[var(--border-medium)] hover:bg-[var(--bg-tag)] transition-colors"
135+
>
136+
<svg className="w-5 h-5 shrink-0" viewBox="0 0 24 24">
137+
<path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" fill="#4285F4" />
138+
<path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" fill="#34A853" />
139+
<path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l3.66-2.84z" fill="#FBBC05" />
140+
<path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" fill="#EA4335" />
141+
</svg>
142+
Continue with Google
143+
</a>
144+
145+
{/* GitHub */}
146+
<a
147+
href="/api/auth/login?provider=github"
148+
className="flex items-center gap-3 font-inter text-[15px] font-semibold text-white rounded-xl px-4 py-3 bg-[#24292e] hover:bg-[#1a1e22] transition-colors"
149+
>
150+
<svg className="w-5 h-5 shrink-0" fill="currentColor" viewBox="0 0 24 24">
151+
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
152+
</svg>
153+
Continue with GitHub
154+
</a>
155+
</div>
156+
</div>
157+
</div>
158+
</>
159+
)}
160+
```
161+
162+
**Step 4: Verify the file compiles**
163+
164+
```bash
165+
npx tsc --noEmit
166+
```
167+
168+
Expected: no errors
169+
170+
**Step 5: Commit**
171+
172+
```bash
173+
git add src/components/Navbar.tsx
174+
git commit -m "feat: replace login buttons with Log In modal in Navbar"
175+
```
176+
177+
---
178+
179+
### Task 3: Manual smoke test
180+
181+
**Step 1: Start dev server**
182+
183+
```bash
184+
npm run dev
185+
```
186+
187+
**Step 2: Verify checklist**
188+
189+
- [ ] Navbar shows single "Log In" button when logged out
190+
- [ ] Clicking "Log In" opens the modal with Google and GitHub options
191+
- [ ] Clicking the overlay (outside the card) closes the modal
192+
- [ ] Clicking the × button closes the modal
193+
- [ ] Clicking "Continue with Google" redirects to `/api/auth/login?provider=google`
194+
- [ ] Clicking "Continue with GitHub" redirects to `/api/auth/login?provider=github`
195+
- [ ] Switching locale (EN ↔ 中文) shows correct translated text in modal title/subtitle
196+
- [ ] Logged-in state still shows username + Logout (unchanged)
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# Supabase OAuth 替换 SecondMe OAuth 设计文档
2+
3+
**日期:** 2026-02-28
4+
**分支:** feature/OAuth
5+
6+
## 背景
7+
8+
将现有的 SecondMe OAuth 认证替换为 Supabase OAuth,支持 Google 和 GitHub 登录。同时删除所有依赖 SecondMe access token 的 API 路由。
9+
10+
## 技术选型
11+
12+
- **认证库:** `@supabase/ssr`(官方推荐的 Next.js App Router 集成方案)
13+
- **数据库:** 保留 Prisma + PostgreSQL,新增 `supabaseUserId` 字段替换 `secondmeUserId`
14+
- **Session 管理:** Supabase cookie-based session(`@supabase/ssr` 自动处理刷新)+ 内部 `session_user_id` cookie 指向 Prisma User
15+
16+
## Auth 流程
17+
18+
```
19+
用户点击 "Sign in with Google/GitHub"
20+
→ GET /api/auth/login?provider=google|github
21+
→ supabase.auth.signInWithOAuth({ provider, redirectTo })
22+
→ 302 到 Google/GitHub 授权页
23+
→ 授权成功 → 302 到 /api/auth/callback?code=...
24+
→ supabase.auth.exchangeCodeForSession(code)
25+
→ 获取 supabase user (email, name, avatar_url)
26+
→ upsert Prisma User (where: supabaseUserId)
27+
→ 设置 session_user_id cookie (Prisma user ID)
28+
→ 302 到 /
29+
```
30+
31+
## 文件改动
32+
33+
### 新增
34+
- `src/lib/supabase.ts` — Supabase browser client + server client 工厂(基于 `@supabase/ssr`
35+
36+
### 修改
37+
| 文件 | 改动描述 |
38+
|------|---------|
39+
| `package.json` | 添加 `@supabase/ssr` |
40+
| `src/lib/auth.ts` | 删除 `getValidAccessToken`(SecondMe token 刷新逻辑),保留 `getSessionUserId / setSessionUserId / clearSession / getCurrentUser` |
41+
| `src/app/api/auth/login/route.ts` | 读取 `provider` query param,调用 `supabase.auth.signInWithOAuth()` |
42+
| `src/app/api/auth/callback/route.ts` | 调用 `exchangeCodeForSession`,upsert Prisma User,保留 claimCode 逻辑 |
43+
| `src/app/api/auth/logout/route.ts` | 调用 `supabase.auth.signOut()`,清除 session cookie |
44+
| `src/middleware.ts` | 添加 Supabase session 刷新(createServerClient + getUser) |
45+
| `prisma/schema.prisma` | User 模型:`secondmeUserId``supabaseUserId`,删除 `accessToken/refreshToken/tokenExpiresAt`;删除 `ChatSession/ChatMessage/Note` 模型 |
46+
| `src/components/Navbar.tsx` | 登录区域改为 Google / GitHub 两个按钮(`/api/auth/login?provider=google``?provider=github`|
47+
48+
### 删除
49+
- `src/app/api/user/info/route.ts`
50+
- `src/app/api/user/shades/route.ts`
51+
- `src/app/api/chat/route.ts`
52+
- `src/app/api/act/route.ts`
53+
- `src/app/api/note/route.ts`
54+
- `src/app/api/sessions/route.ts`
55+
56+
## Prisma Schema 变更
57+
58+
```prisma
59+
model User {
60+
id String @id @default(cuid())
61+
supabaseUserId String @unique @map("supabase_user_id") // 替换 secondmeUserId
62+
email String?
63+
name String?
64+
avatarUrl String? @map("avatar_url")
65+
// 删除: accessToken, refreshToken, tokenExpiresAt
66+
// 其余字段保持不变
67+
...
68+
}
69+
// 删除: ChatSession, ChatMessage, Note 模型
70+
```
71+
72+
## 环境变量
73+
74+
新增:
75+
```
76+
NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co
77+
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJ...
78+
```
79+
80+
删除:
81+
```
82+
SECONDME_CLIENT_ID
83+
SECONDME_CLIENT_SECRET
84+
SECONDME_OAUTH_URL
85+
SECONDME_REDIRECT_URI
86+
SECONDME_TOKEN_ENDPOINT
87+
SECONDME_API_BASE_URL
88+
```
89+
90+
## Supabase 控制台配置
91+
92+
在 Supabase Dashboard 中需要配置:
93+
1. Authentication → Providers → 启用 Google(填写 Client ID + Secret)
94+
2. Authentication → Providers → 启用 GitHub(填写 Client ID + Secret)
95+
3. Authentication → URL Configuration → 添加 Redirect URL:`{APP_URL}/api/auth/callback`

0 commit comments

Comments
 (0)