dramaling-vocab-learning/web-setup-guide.md

13 KiB
Raw Blame History

LinguaForge 網頁版環境設置指南

🚀 快速開始15分鐘

完整指令(複製貼上即可)

# 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 或其他編輯器

必要帳號

🔧 詳細設置步驟

Step 1: Supabase 設置

1.1 建立專案

  1. 前往 app.supabase.com
  2. 點擊「New project」
  3. 設定:
    • Project name: linguaforge
    • Database Password: 設定強密碼
    • Region: Southeast Asia (Singapore) (離台灣近)

1.2 建立資料表

在 SQL Editor 執行:

-- 啟用 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
  2. 點擊「Get API key」
  3. 選擇或建立 Google Cloud 專案
  4. 複製 API Key

2.2 測試 API

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

# 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

{
  "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

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: 建立專案結構

# 建立資料夾結構
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

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

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

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: 測試運行

# 啟動開發伺服器
npm run dev

# 開啟瀏覽器
open http://localhost:3000

🚀 部署到 Vercel

6.1 準備部署

# 初始化 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
  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 設置

推薦擴充套件

{
  "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

{
  "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 可以呼叫

效能測試

# Lighthouse 測試
npm run build
npm run start
# 開啟 Chrome DevTools > Lighthouse

🐛 常見問題

1. Supabase 連線失敗

# 檢查環境變數
echo $NEXT_PUBLIC_SUPABASE_URL
echo $NEXT_PUBLIC_SUPABASE_ANON_KEY

# 確認 .env.local 有被載入
# 重啟開發伺服器

2. Gemini API 錯誤

# 檢查 API Key
curl "https://generativelanguage.googleapis.com/v1beta/models?key=YOUR_KEY"

# 檢查配額
# 前往 Google Cloud Console 查看

3. TypeScript 錯誤

# 重新生成類型
npm run build

# 清除快取
rm -rf .next
npm run dev

4. Vercel 部署失敗

# 本地測試 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 了!