530 lines
13 KiB
Markdown
530 lines
13 KiB
Markdown
# LinguaForge 網頁版環境設置指南
|
||
|
||
## 🚀 快速開始(15分鐘)
|
||
|
||
### 完整指令(複製貼上即可)
|
||
```bash
|
||
# 1. 建立專案
|
||
npx create-next-app@latest linguaforge-web --typescript --tailwind --app --src-dir=false --import-alias="@/*"
|
||
cd linguaforge-web
|
||
|
||
# 2. 安裝所有套件
|
||
npm install @supabase/supabase-js @supabase/ssr @supabase/auth-helpers-nextjs
|
||
npm install zustand @tanstack/react-query @google/generative-ai
|
||
npm install next-pwa next-themes
|
||
npm install lucide-react date-fns zod
|
||
npm install -D @types/node
|
||
|
||
# 3. 安裝 shadcn/ui
|
||
npx shadcn-ui@latest init -y
|
||
|
||
# 4. 安裝常用元件
|
||
npx shadcn-ui@latest add button card dialog form input label textarea toast alert badge skeleton tabs
|
||
|
||
# 5. 建立環境變數檔
|
||
touch .env.local
|
||
|
||
# 6. 啟動開發伺服器
|
||
npm run dev
|
||
```
|
||
|
||
## 📋 前置需求檢查
|
||
|
||
### 必要工具
|
||
- [ ] Node.js 18+ (檢查:`node -v`)
|
||
- [ ] npm 或 pnpm (檢查:`npm -v`)
|
||
- [ ] Git (檢查:`git --version`)
|
||
- [ ] VS Code 或其他編輯器
|
||
|
||
### 必要帳號
|
||
- [ ] [Supabase](https://supabase.com) - 資料庫
|
||
- [ ] [Google AI Studio](https://makersuite.google.com) - Gemini API
|
||
- [ ] [Vercel](https://vercel.com) - 部署平台
|
||
- [ ] [GitHub](https://github.com) - 版本控制
|
||
|
||
## 🔧 詳細設置步驟
|
||
|
||
### Step 1: Supabase 設置
|
||
|
||
#### 1.1 建立專案
|
||
1. 前往 [app.supabase.com](https://app.supabase.com)
|
||
2. 點擊「New project」
|
||
3. 設定:
|
||
- Project name: `linguaforge`
|
||
- Database Password: 設定強密碼
|
||
- Region: `Southeast Asia (Singapore)` (離台灣近)
|
||
|
||
#### 1.2 建立資料表
|
||
在 SQL Editor 執行:
|
||
|
||
```sql
|
||
-- 啟用 UUID 擴充
|
||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||
|
||
-- 用戶表(使用 Supabase Auth)
|
||
CREATE TABLE profiles (
|
||
id UUID REFERENCES auth.users(id) ON DELETE CASCADE PRIMARY KEY,
|
||
username TEXT UNIQUE,
|
||
created_at TIMESTAMP WITH TIME ZONE DEFAULT TIMEZONE('utc', NOW()),
|
||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT TIMEZONE('utc', NOW())
|
||
);
|
||
|
||
-- 詞卡表
|
||
CREATE TABLE cards (
|
||
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
|
||
user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE NOT NULL,
|
||
word TEXT NOT NULL,
|
||
pronunciation TEXT,
|
||
definition TEXT NOT NULL,
|
||
part_of_speech TEXT,
|
||
examples JSONB DEFAULT '[]',
|
||
source_sentence TEXT,
|
||
difficulty TEXT CHECK (difficulty IN ('beginner', 'intermediate', 'advanced')),
|
||
|
||
-- SM-2 演算法欄位
|
||
next_review_date TIMESTAMP WITH TIME ZONE DEFAULT TIMEZONE('utc', NOW()),
|
||
easiness_factor DECIMAL(3,2) DEFAULT 2.5,
|
||
interval_days INTEGER DEFAULT 0,
|
||
repetition_count INTEGER DEFAULT 0,
|
||
|
||
created_at TIMESTAMP WITH TIME ZONE DEFAULT TIMEZONE('utc', NOW()),
|
||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT TIMEZONE('utc', NOW()),
|
||
|
||
INDEX idx_user_cards (user_id),
|
||
INDEX idx_next_review (user_id, next_review_date)
|
||
);
|
||
|
||
-- 複習記錄表
|
||
CREATE TABLE review_logs (
|
||
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
|
||
card_id UUID REFERENCES cards(id) ON DELETE CASCADE NOT NULL,
|
||
user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE NOT NULL,
|
||
quality INTEGER CHECK (quality >= 0 AND quality <= 5) NOT NULL,
|
||
reviewed_at TIMESTAMP WITH TIME ZONE DEFAULT TIMEZONE('utc', NOW()),
|
||
time_spent_seconds INTEGER,
|
||
|
||
INDEX idx_user_reviews (user_id, reviewed_at)
|
||
);
|
||
|
||
-- 啟用 Row Level Security
|
||
ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;
|
||
ALTER TABLE cards ENABLE ROW LEVEL SECURITY;
|
||
ALTER TABLE review_logs ENABLE ROW LEVEL SECURITY;
|
||
|
||
-- RLS 政策
|
||
CREATE POLICY "Users can view own profile" ON profiles
|
||
FOR SELECT USING (auth.uid() = id);
|
||
|
||
CREATE POLICY "Users can update own profile" ON profiles
|
||
FOR UPDATE USING (auth.uid() = id);
|
||
|
||
CREATE POLICY "Users can create own profile" ON profiles
|
||
FOR INSERT WITH CHECK (auth.uid() = id);
|
||
|
||
CREATE POLICY "Users can view own cards" ON cards
|
||
FOR SELECT USING (auth.uid() = user_id);
|
||
|
||
CREATE POLICY "Users can create own cards" ON cards
|
||
FOR INSERT WITH CHECK (auth.uid() = user_id);
|
||
|
||
CREATE POLICY "Users can update own cards" ON cards
|
||
FOR UPDATE USING (auth.uid() = user_id);
|
||
|
||
CREATE POLICY "Users can delete own cards" ON cards
|
||
FOR DELETE USING (auth.uid() = user_id);
|
||
|
||
CREATE POLICY "Users can view own reviews" ON review_logs
|
||
FOR SELECT USING (auth.uid() = user_id);
|
||
|
||
CREATE POLICY "Users can create own reviews" ON review_logs
|
||
FOR INSERT WITH CHECK (auth.uid() = user_id);
|
||
|
||
-- 觸發器:自動更新 updated_at
|
||
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
||
RETURNS TRIGGER AS $$
|
||
BEGIN
|
||
NEW.updated_at = TIMEZONE('utc', NOW());
|
||
RETURN NEW;
|
||
END;
|
||
$$ language 'plpgsql';
|
||
|
||
CREATE TRIGGER update_profiles_updated_at BEFORE UPDATE ON profiles
|
||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||
|
||
CREATE TRIGGER update_cards_updated_at BEFORE UPDATE ON cards
|
||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||
```
|
||
|
||
#### 1.3 取得 API Keys
|
||
1. 進入 Project Settings > API
|
||
2. 複製:
|
||
- `URL` (Project URL)
|
||
- `anon public` key
|
||
|
||
### Step 2: Gemini API 設置
|
||
|
||
#### 2.1 取得 API Key
|
||
1. 前往 [Google AI Studio](https://makersuite.google.com/app/apikey)
|
||
2. 點擊「Get API key」
|
||
3. 選擇或建立 Google Cloud 專案
|
||
4. 複製 API Key
|
||
|
||
#### 2.2 測試 API
|
||
```bash
|
||
curl -X POST "https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key=YOUR_API_KEY" \
|
||
-H "Content-Type: application/json" \
|
||
-d '{
|
||
"contents": [{
|
||
"parts": [{
|
||
"text": "Hello"
|
||
}]
|
||
}]
|
||
}'
|
||
```
|
||
|
||
### Step 3: 專案配置
|
||
|
||
#### 3.1 環境變數設置
|
||
編輯 `.env.local`:
|
||
|
||
```bash
|
||
# Supabase
|
||
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
|
||
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
|
||
|
||
# Gemini API
|
||
GEMINI_API_KEY=your-gemini-api-key
|
||
|
||
# App
|
||
NEXT_PUBLIC_APP_URL=http://localhost:3000
|
||
```
|
||
|
||
#### 3.2 TypeScript 配置
|
||
更新 `tsconfig.json`:
|
||
|
||
```json
|
||
{
|
||
"compilerOptions": {
|
||
"target": "ES2017",
|
||
"lib": ["dom", "dom.iterable", "esnext"],
|
||
"allowJs": true,
|
||
"skipLibCheck": true,
|
||
"strict": true,
|
||
"noEmit": true,
|
||
"esModuleInterop": true,
|
||
"module": "esnext",
|
||
"moduleResolution": "bundler",
|
||
"resolveJsonModule": true,
|
||
"isolatedModules": true,
|
||
"jsx": "preserve",
|
||
"incremental": true,
|
||
"plugins": [
|
||
{
|
||
"name": "next"
|
||
}
|
||
],
|
||
"paths": {
|
||
"@/*": ["./*"]
|
||
}
|
||
},
|
||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||
"exclude": ["node_modules"]
|
||
}
|
||
```
|
||
|
||
#### 3.3 Tailwind 配置
|
||
更新 `tailwind.config.ts`:
|
||
|
||
```typescript
|
||
import type { Config } from 'tailwindcss'
|
||
|
||
const config: Config = {
|
||
darkMode: ["class"],
|
||
content: [
|
||
'./pages/**/*.{ts,tsx}',
|
||
'./components/**/*.{ts,tsx}',
|
||
'./app/**/*.{ts,tsx}',
|
||
],
|
||
theme: {
|
||
extend: {
|
||
colors: {
|
||
border: "hsl(var(--border))",
|
||
input: "hsl(var(--input))",
|
||
ring: "hsl(var(--ring))",
|
||
background: "hsl(var(--background))",
|
||
foreground: "hsl(var(--foreground))",
|
||
primary: {
|
||
DEFAULT: "hsl(var(--primary))",
|
||
foreground: "hsl(var(--primary-foreground))",
|
||
},
|
||
secondary: {
|
||
DEFAULT: "hsl(var(--secondary))",
|
||
foreground: "hsl(var(--secondary-foreground))",
|
||
},
|
||
destructive: {
|
||
DEFAULT: "hsl(var(--destructive))",
|
||
foreground: "hsl(var(--destructive-foreground))",
|
||
},
|
||
muted: {
|
||
DEFAULT: "hsl(var(--muted))",
|
||
foreground: "hsl(var(--muted-foreground))",
|
||
},
|
||
accent: {
|
||
DEFAULT: "hsl(var(--accent))",
|
||
foreground: "hsl(var(--accent-foreground))",
|
||
},
|
||
},
|
||
borderRadius: {
|
||
lg: "var(--radius)",
|
||
md: "calc(var(--radius) - 2px)",
|
||
sm: "calc(var(--radius) - 4px)",
|
||
},
|
||
},
|
||
},
|
||
plugins: [require("tailwindcss-animate")],
|
||
}
|
||
|
||
export default config
|
||
```
|
||
|
||
### Step 4: 建立專案結構
|
||
|
||
```bash
|
||
# 建立資料夾結構
|
||
mkdir -p app/{api,\(auth\),\(dashboard\)}
|
||
mkdir -p app/api/{gemini,cards,review}
|
||
mkdir -p app/\(auth\)/{login,register}
|
||
mkdir -p app/\(dashboard\)/{cards,generate,review,stats}
|
||
mkdir -p components/{ui,cards,layout,providers}
|
||
mkdir -p lib/{supabase,gemini,algorithms}
|
||
mkdir -p hooks
|
||
mkdir -p types
|
||
mkdir -p public/{icons,images}
|
||
```
|
||
|
||
### Step 5: 初始化核心檔案
|
||
|
||
#### 5.1 Supabase Client
|
||
建立 `lib/supabase/client.ts`:
|
||
|
||
```typescript
|
||
import { createBrowserClient } from '@supabase/ssr'
|
||
|
||
export function createClient() {
|
||
return createBrowserClient(
|
||
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
||
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
|
||
)
|
||
}
|
||
```
|
||
|
||
#### 5.2 根 Layout
|
||
更新 `app/layout.tsx`:
|
||
|
||
```tsx
|
||
import type { Metadata } from 'next'
|
||
import { Inter } from 'next/font/google'
|
||
import './globals.css'
|
||
|
||
const inter = Inter({ subsets: ['latin'] })
|
||
|
||
export const metadata: Metadata = {
|
||
title: 'LinguaForge - AI 英語詞彙學習',
|
||
description: 'AI 驅動的個人化英語詞彙學習平台',
|
||
}
|
||
|
||
export default function RootLayout({
|
||
children,
|
||
}: {
|
||
children: React.ReactNode
|
||
}) {
|
||
return (
|
||
<html lang="zh-TW">
|
||
<body className={inter.className}>{children}</body>
|
||
</html>
|
||
)
|
||
}
|
||
```
|
||
|
||
#### 5.3 首頁
|
||
更新 `app/page.tsx`:
|
||
|
||
```tsx
|
||
import Link from 'next/link'
|
||
import { Button } from '@/components/ui/button'
|
||
|
||
export default function HomePage() {
|
||
return (
|
||
<div className="flex min-h-screen flex-col items-center justify-center">
|
||
<div className="text-center space-y-6">
|
||
<h1 className="text-4xl font-bold">LinguaForge</h1>
|
||
<p className="text-xl text-muted-foreground">
|
||
AI 驅動的英語詞彙學習平台
|
||
</p>
|
||
<div className="flex gap-4 justify-center">
|
||
<Button asChild>
|
||
<Link href="/register">開始學習</Link>
|
||
</Button>
|
||
<Button variant="outline" asChild>
|
||
<Link href="/login">登入</Link>
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
```
|
||
|
||
### Step 6: 測試運行
|
||
|
||
```bash
|
||
# 啟動開發伺服器
|
||
npm run dev
|
||
|
||
# 開啟瀏覽器
|
||
open http://localhost:3000
|
||
```
|
||
|
||
## 🚀 部署到 Vercel
|
||
|
||
### 6.1 準備部署
|
||
```bash
|
||
# 初始化 Git
|
||
git init
|
||
git add .
|
||
git commit -m "Initial commit"
|
||
|
||
# 推送到 GitHub
|
||
git remote add origin https://github.com/your-username/linguaforge-web.git
|
||
git push -u origin main
|
||
```
|
||
|
||
### 6.2 Vercel 部署
|
||
1. 前往 [vercel.com](https://vercel.com)
|
||
2. 點擊「Import Project」
|
||
3. 選擇 GitHub repo
|
||
4. 設定環境變數:
|
||
- `NEXT_PUBLIC_SUPABASE_URL`
|
||
- `NEXT_PUBLIC_SUPABASE_ANON_KEY`
|
||
- `GEMINI_API_KEY`
|
||
5. 點擊「Deploy」
|
||
|
||
### 6.3 自訂網域(可選)
|
||
1. 在 Vercel Dashboard > Settings > Domains
|
||
2. 新增自訂網域
|
||
3. 按照指示設定 DNS
|
||
|
||
## 🛠️ VS Code 設置
|
||
|
||
### 推薦擴充套件
|
||
```json
|
||
{
|
||
"recommendations": [
|
||
"dbaeumer.vscode-eslint",
|
||
"esbenp.prettier-vscode",
|
||
"bradlc.vscode-tailwindcss",
|
||
"prisma.prisma",
|
||
"ms-vscode.vscode-typescript-next",
|
||
"christian-kohler.path-intellisense",
|
||
"aaron-bond.better-comments",
|
||
"usernamehw.errorlens"
|
||
]
|
||
}
|
||
```
|
||
|
||
### 工作區設定
|
||
`.vscode/settings.json`:
|
||
|
||
```json
|
||
{
|
||
"editor.formatOnSave": true,
|
||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||
"editor.codeActionsOnSave": {
|
||
"source.fixAll.eslint": true
|
||
},
|
||
"typescript.tsdk": "node_modules/typescript/lib",
|
||
"typescript.enablePromptUseWorkspaceTsdk": true,
|
||
"tailwindCSS.experimental.classRegex": [
|
||
["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"],
|
||
["cx\\(([^)]*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"]
|
||
]
|
||
}
|
||
```
|
||
|
||
## 🧪 測試檢查清單
|
||
|
||
### 功能測試
|
||
- [ ] 首頁正常顯示
|
||
- [ ] Supabase 連線成功
|
||
- [ ] 可以註冊新用戶
|
||
- [ ] 可以登入
|
||
- [ ] Gemini API 可以呼叫
|
||
|
||
### 效能測試
|
||
```bash
|
||
# Lighthouse 測試
|
||
npm run build
|
||
npm run start
|
||
# 開啟 Chrome DevTools > Lighthouse
|
||
```
|
||
|
||
## 🐛 常見問題
|
||
|
||
### 1. Supabase 連線失敗
|
||
```bash
|
||
# 檢查環境變數
|
||
echo $NEXT_PUBLIC_SUPABASE_URL
|
||
echo $NEXT_PUBLIC_SUPABASE_ANON_KEY
|
||
|
||
# 確認 .env.local 有被載入
|
||
# 重啟開發伺服器
|
||
```
|
||
|
||
### 2. Gemini API 錯誤
|
||
```bash
|
||
# 檢查 API Key
|
||
curl "https://generativelanguage.googleapis.com/v1beta/models?key=YOUR_KEY"
|
||
|
||
# 檢查配額
|
||
# 前往 Google Cloud Console 查看
|
||
```
|
||
|
||
### 3. TypeScript 錯誤
|
||
```bash
|
||
# 重新生成類型
|
||
npm run build
|
||
|
||
# 清除快取
|
||
rm -rf .next
|
||
npm run dev
|
||
```
|
||
|
||
### 4. Vercel 部署失敗
|
||
```bash
|
||
# 本地測試 production build
|
||
npm run build
|
||
npm run start
|
||
|
||
# 檢查環境變數
|
||
vercel env pull
|
||
```
|
||
|
||
## 📝 下一步
|
||
|
||
環境設置完成後:
|
||
1. 參考 `web-mvp-master-plan.md` 開始開發
|
||
2. 參考 `web-technical-architecture.md` 了解技術細節
|
||
3. 開始 Week 1 的任務
|
||
|
||
## 🎉 完成確認
|
||
|
||
如果以下都完成,你就可以開始開發了:
|
||
- ✅ 專案在 http://localhost:3000 運行
|
||
- ✅ Supabase 資料表建立完成
|
||
- ✅ Gemini API Key 測試成功
|
||
- ✅ Git repository 初始化
|
||
- ✅ 首次 commit 完成
|
||
|
||
**恭喜!你的開發環境已經準備就緒!🚀**
|
||
|
||
現在可以開始按照 6 週計劃開發你的 MVP 了! |