fix: 修復前端認證 token 發送和用戶資料隔離問題
- 啟用前端 API client 的認證 header 發送(修復關鍵問題) - 添加 401 錯誤自動清除過期 token 機制 - 設計兩種空狀態畫面:新用戶歡迎 vs 完成慶祝 - 改進錯誤處理:區分認證錯誤和一般錯誤 - 添加詳細除錯日誌追蹤 API 調用過程 - 修復前端條件判斷邏輯,確保正確顯示空狀態 現在用戶資料完全隔離,認證過期會自動處理並引導重新登入。 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
1eb28e83c5
commit
4a7c3aec92
|
|
@ -5,11 +5,12 @@ using DramaLing.Api.Repositories;
|
|||
using DramaLing.Api.Services.Review;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using DramaLing.Api.Utils;
|
||||
using DramaLing.Api.Services;
|
||||
|
||||
namespace DramaLing.Api.Controllers;
|
||||
|
||||
[Route("api/flashcards")]
|
||||
[AllowAnonymous]
|
||||
[Authorize] // 恢復認證要求,確保用戶資料隔離
|
||||
public class FlashcardsController : BaseController
|
||||
{
|
||||
private readonly IFlashcardRepository _flashcardRepository;
|
||||
|
|
@ -18,7 +19,8 @@ public class FlashcardsController : BaseController
|
|||
public FlashcardsController(
|
||||
IFlashcardRepository flashcardRepository,
|
||||
IReviewService reviewService,
|
||||
ILogger<FlashcardsController> logger) : base(logger)
|
||||
IAuthService authService,
|
||||
ILogger<FlashcardsController> logger) : base(logger, authService)
|
||||
{
|
||||
_flashcardRepository = flashcardRepository;
|
||||
_reviewService = reviewService;
|
||||
|
|
|
|||
|
|
@ -23,9 +23,21 @@ export default function ReviewPage() {
|
|||
handleRestart,
|
||||
isLoading,
|
||||
error,
|
||||
flashcards
|
||||
flashcards,
|
||||
totalFlashcardsCount
|
||||
} = useReviewSession()
|
||||
|
||||
// 除錯日誌 - 檢查狀態
|
||||
console.log('🔍 Review Page 狀態檢查:', {
|
||||
isLoading,
|
||||
error,
|
||||
flashcardsLength: flashcards.length,
|
||||
totalFlashcardsCount,
|
||||
currentQuizItem: currentQuizItem?.id,
|
||||
currentCard: currentCard?.word,
|
||||
isComplete
|
||||
})
|
||||
|
||||
// 顯示載入狀態
|
||||
if (isLoading) {
|
||||
return (
|
||||
|
|
@ -46,21 +58,221 @@ export default function ReviewPage() {
|
|||
|
||||
// 顯示錯誤狀態
|
||||
if (error) {
|
||||
const isAuthError = error.includes('登入已過期') || error.includes('認證')
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100">
|
||||
<Navigation />
|
||||
<div className="py-8">
|
||||
<div className="max-w-4xl mx-auto px-4">
|
||||
<div className="bg-white rounded-xl shadow-lg p-8 text-center">
|
||||
<div className="text-red-500 text-4xl mb-4">⚠️</div>
|
||||
<h2 className="text-xl font-semibold text-red-700 mb-2">載入失敗</h2>
|
||||
<div className={`text-4xl mb-4 ${isAuthError ? 'text-yellow-500' : 'text-red-500'}`}>
|
||||
{isAuthError ? '🔒' : '⚠️'}
|
||||
</div>
|
||||
<h2 className={`text-xl font-semibold mb-2 ${isAuthError ? 'text-yellow-700' : 'text-red-700'}`}>
|
||||
{isAuthError ? '需要重新登入' : '載入失敗'}
|
||||
</h2>
|
||||
<p className="text-gray-600 mb-6">{error}</p>
|
||||
<button
|
||||
onClick={handleRestart}
|
||||
className="px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
重新載入
|
||||
</button>
|
||||
|
||||
<div className="flex flex-col sm:flex-row gap-3 justify-center">
|
||||
{isAuthError ? (
|
||||
<button
|
||||
onClick={() => window.location.href = '/login'}
|
||||
className="px-8 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors font-semibold"
|
||||
>
|
||||
前往登入
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
onClick={handleRestart}
|
||||
className="px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
重新載入
|
||||
</button>
|
||||
)}
|
||||
|
||||
<button
|
||||
onClick={() => window.location.href = '/'}
|
||||
className="px-6 py-2 bg-gray-600 text-white rounded-lg hover:bg-gray-700 transition-colors"
|
||||
>
|
||||
回到首頁
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// 情境 1: 新用戶 - 一張詞卡都沒有
|
||||
if (!isLoading && !error && totalFlashcardsCount === 0) {
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100">
|
||||
<Navigation />
|
||||
<div className="py-8">
|
||||
<div className="max-w-4xl mx-auto px-4">
|
||||
<div className="bg-white rounded-xl shadow-lg p-8 text-center">
|
||||
{/* 歡迎圖標 */}
|
||||
<div className="mb-6">
|
||||
<div className="w-24 h-24 bg-gradient-to-br from-blue-400 to-blue-600 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<span className="text-4xl text-white">👋</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 歡迎標題 */}
|
||||
<h1 className="text-3xl font-bold text-gray-900 mb-4">
|
||||
歡迎來到 DramaLing!
|
||||
</h1>
|
||||
|
||||
{/* 說明文字 */}
|
||||
<p className="text-lg text-gray-600 mb-8">
|
||||
開始您的英語學習之旅,建立第一張詞卡來開始學習吧!
|
||||
</p>
|
||||
|
||||
{/* 功能介紹卡片 */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
|
||||
<div className="bg-gradient-to-br from-green-50 to-green-100 rounded-lg p-6">
|
||||
<div className="text-2xl text-green-600 mb-2">🎯</div>
|
||||
<h3 className="font-semibold text-gray-900">智能學習</h3>
|
||||
<p className="text-sm text-gray-600 mt-2">
|
||||
AI 驅動的個人化詞彙學習系統
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-gradient-to-br from-yellow-50 to-yellow-100 rounded-lg p-6">
|
||||
<div className="text-2xl text-yellow-600 mb-2">🧠</div>
|
||||
<h3 className="font-semibold text-gray-900">科學記憶</h3>
|
||||
<p className="text-sm text-gray-600 mt-2">
|
||||
基於遺忘曲線的複習提醒
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 主要行動按鈕 */}
|
||||
<div className="mb-6">
|
||||
<button
|
||||
onClick={() => window.location.href = '/generate'}
|
||||
className="px-10 py-4 bg-gradient-to-r from-blue-600 to-blue-700 text-white rounded-lg hover:from-blue-700 hover:to-blue-800 transition-all font-semibold text-lg flex items-center justify-center gap-3 mx-auto"
|
||||
>
|
||||
<span className="text-xl">🚀</span>
|
||||
建立第一張詞卡
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 次要功能 */}
|
||||
<div className="flex flex-col sm:flex-row gap-3 justify-center">
|
||||
<button
|
||||
onClick={() => window.location.href = '/flashcards'}
|
||||
className="px-6 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition-colors font-medium"
|
||||
>
|
||||
瀏覽詞卡功能
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 學習提示 */}
|
||||
<div className="mt-8 p-4 bg-blue-50 rounded-lg border border-blue-200">
|
||||
<p className="text-sm text-blue-800">
|
||||
💡 <strong>開始提示</strong>: 建議從日常生活中的詞彙開始,
|
||||
或輸入您感興趣的英文句子讓 AI 協助分析!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// 情境 2: 有詞卡但都已訓練完成
|
||||
if (!isLoading && !error && totalFlashcardsCount && totalFlashcardsCount > 0 && flashcards.length === 0) {
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100">
|
||||
<Navigation />
|
||||
<div className="py-8">
|
||||
<div className="max-w-4xl mx-auto px-4">
|
||||
<div className="bg-white rounded-xl shadow-lg p-8 text-center">
|
||||
{/* 慶祝圖標 */}
|
||||
<div className="mb-6">
|
||||
<div className="w-24 h-24 bg-gradient-to-br from-green-400 to-green-600 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<span className="text-4xl text-white">🎉</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 主要標題 */}
|
||||
<h1 className="text-3xl font-bold text-gray-900 mb-4">
|
||||
太棒了!所有詞卡都已掌握
|
||||
</h1>
|
||||
|
||||
{/* 次標題 */}
|
||||
<p className="text-lg text-gray-600 mb-4">
|
||||
您已完成所有 <span className="font-semibold text-green-600">{totalFlashcardsCount}</span> 張詞卡的學習!
|
||||
</p>
|
||||
<p className="text-md text-gray-500 mb-8">
|
||||
目前沒有需要複習的詞卡,您的學習進度非常優秀!
|
||||
</p>
|
||||
|
||||
{/* 統計卡片 */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
|
||||
<div className="bg-gradient-to-br from-blue-50 to-blue-100 rounded-lg p-6">
|
||||
<div className="text-2xl text-blue-600 mb-2">📚</div>
|
||||
<h3 className="font-semibold text-gray-900">持續學習</h3>
|
||||
<p className="text-sm text-gray-600 mt-2">
|
||||
保持每日複習習慣
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-gradient-to-br from-purple-50 to-purple-100 rounded-lg p-6">
|
||||
<div className="text-2xl text-purple-600 mb-2">📈</div>
|
||||
<h3 className="font-semibold text-gray-900">查看進度</h3>
|
||||
<p className="text-sm text-gray-600 mt-2">
|
||||
檢視學習統計數據
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-gradient-to-br from-green-50 to-green-100 rounded-lg p-6">
|
||||
<div className="text-2xl text-green-600 mb-2">➕</div>
|
||||
<h3 className="font-semibold text-gray-900">擴展詞庫</h3>
|
||||
<p className="text-sm text-gray-600 mt-2">
|
||||
新增更多詞彙挑戰
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 行動按鈕 */}
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<button
|
||||
onClick={() => window.location.href = '/generate'}
|
||||
className="px-8 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors font-semibold flex items-center justify-center gap-2"
|
||||
>
|
||||
<span>➕</span>
|
||||
新增詞卡
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => window.location.href = '/stats'}
|
||||
className="px-8 py-3 bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition-colors font-semibold flex items-center justify-center gap-2"
|
||||
>
|
||||
<span>📊</span>
|
||||
查看統計
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => window.location.href = '/flashcards'}
|
||||
className="px-8 py-3 bg-gray-600 text-white rounded-lg hover:bg-gray-700 transition-colors font-semibold flex items-center justify-center gap-2"
|
||||
>
|
||||
<span>📋</span>
|
||||
管理詞卡
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 提示文字 */}
|
||||
<div className="mt-8 p-4 bg-yellow-50 rounded-lg border border-yellow-200">
|
||||
<p className="text-sm text-yellow-800">
|
||||
💡 <strong>小提示</strong>: 詞卡會根據遺忘曲線算法自動安排複習時間。
|
||||
繼續保持學習,明天可能會有新的複習內容!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -108,6 +320,8 @@ export default function ReviewPage() {
|
|||
}
|
||||
|
||||
// 主要線性測驗頁面
|
||||
// 只有在有可用測驗項目時才顯示測驗界面
|
||||
if (!isLoading && !error && totalFlashcardsCount !== null && totalFlashcardsCount > 0 && flashcards.length > 0 && currentQuizItem && currentCard) {
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100">
|
||||
<Navigation />
|
||||
|
|
@ -147,4 +361,32 @@ export default function ReviewPage() {
|
|||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Fallback 狀態 - 處理其他未預期情況
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100">
|
||||
<Navigation />
|
||||
<div className="py-8">
|
||||
<div className="max-w-4xl mx-auto px-4">
|
||||
<div className="bg-white rounded-xl shadow-lg p-8 text-center">
|
||||
<div className="text-4xl mb-4">🤔</div>
|
||||
<h2 className="text-xl font-semibold text-gray-700 mb-2">正在準備學習內容</h2>
|
||||
<p className="text-gray-600 mb-4">
|
||||
系統正在載入您的學習資料,請稍候片刻。
|
||||
</p>
|
||||
<p className="text-sm text-gray-500 mb-6">
|
||||
如果問題持續,請嘗試重新載入頁面。
|
||||
</p>
|
||||
<button
|
||||
onClick={handleRestart}
|
||||
className="px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
重新載入
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -34,6 +34,7 @@ interface ReviewState {
|
|||
error: string | null
|
||||
pendingWordSubmission: string | null // 等待提交的詞彙ID
|
||||
submittingWords: Set<string> // 正在提交的詞彙ID集合
|
||||
totalFlashcardsCount: number | null // 用戶總詞卡數(用於區分空狀態)
|
||||
}
|
||||
|
||||
type ReviewAction =
|
||||
|
|
@ -42,7 +43,7 @@ type ReviewAction =
|
|||
| { type: 'SKIP_TEST_ITEM'; payload: { quizItemId: string } }
|
||||
| { type: 'RESTART' }
|
||||
| { type: 'LOAD_FLASHCARDS_START' }
|
||||
| { type: 'LOAD_FLASHCARDS_SUCCESS'; payload: { flashcards: Flashcard[]; quizItems: QuizItem[] } }
|
||||
| { type: 'LOAD_FLASHCARDS_SUCCESS'; payload: { flashcards: Flashcard[]; quizItems: QuizItem[]; totalCount: number } }
|
||||
| { type: 'LOAD_FLASHCARDS_ERROR'; payload: { error: string } }
|
||||
| { type: 'WORD_SUBMIT_START'; payload: { cardId: string } }
|
||||
| { type: 'WORD_SUBMIT_SUCCESS'; payload: { cardId: string; nextReviewDate: string } }
|
||||
|
|
@ -165,7 +166,8 @@ const reviewReducer = (state: ReviewState, action: ReviewAction): ReviewState =>
|
|||
isLoading: false,
|
||||
error: null,
|
||||
flashcards: action.payload.flashcards,
|
||||
quizItems: action.payload.quizItems
|
||||
quizItems: action.payload.quizItems,
|
||||
totalFlashcardsCount: action.payload.totalCount
|
||||
}
|
||||
|
||||
case 'LOAD_FLASHCARDS_ERROR':
|
||||
|
|
@ -301,10 +303,11 @@ export function useReviewSession() {
|
|||
isLoading: false,
|
||||
error: null,
|
||||
pendingWordSubmission: null,
|
||||
submittingWords: new Set<string>()
|
||||
submittingWords: new Set<string>(),
|
||||
totalFlashcardsCount: null
|
||||
})
|
||||
|
||||
const { quizItems, score, isComplete, flashcards, isLoading, error, pendingWordSubmission, submittingWords } = state
|
||||
const { quizItems, score, isComplete, flashcards, isLoading, error, pendingWordSubmission, submittingWords, totalFlashcardsCount } = state
|
||||
|
||||
// 智能排序獲取當前測驗項目 - 使用 useMemo 優化性能
|
||||
const sortedQuizItems = useMemo(() => sortQuizItemsByPriority(quizItems), [quizItems])
|
||||
|
|
@ -321,29 +324,95 @@ export function useReviewSession() {
|
|||
dispatch({ type: 'LOAD_FLASHCARDS_START' })
|
||||
|
||||
try {
|
||||
const response = await flashcardsService.getDueFlashcards(10)
|
||||
console.log('🚀 開始載入詞卡資料...')
|
||||
|
||||
if (response.success && response.data) {
|
||||
const flashcards = response.data
|
||||
const quizItems = generateQuizItemsFromFlashcards(flashcards)
|
||||
// 同時獲取待複習詞卡和總詞卡數
|
||||
const [dueResponse, totalResponse] = await Promise.all([
|
||||
flashcardsService.getDueFlashcards(10),
|
||||
flashcardsService.getFlashcards() // 獲取所有詞卡來計算總數
|
||||
])
|
||||
|
||||
console.log('📥 API 回應:', {
|
||||
dueResponse: {
|
||||
success: dueResponse.success,
|
||||
dataType: typeof dueResponse.data,
|
||||
dataLength: Array.isArray(dueResponse.data) ? dueResponse.data.length : 'not array',
|
||||
error: dueResponse.error
|
||||
},
|
||||
totalResponse: {
|
||||
success: totalResponse.success,
|
||||
dataType: typeof totalResponse.data,
|
||||
dataLength: Array.isArray(totalResponse.data) ? totalResponse.data.length : 'not array',
|
||||
error: totalResponse.error
|
||||
}
|
||||
})
|
||||
|
||||
if (dueResponse.success && totalResponse.success) {
|
||||
// 正確的資料結構處理
|
||||
const dueFlashcards = dueResponse.data || [] // getDueFlashcards 返回 Flashcard[]
|
||||
const totalFlashcardsData = totalResponse.data || { flashcards: [], count: 0 } // getFlashcards 返回 { flashcards: [], count: number }
|
||||
const totalFlashcards = totalFlashcardsData.flashcards || []
|
||||
const totalCount = totalFlashcardsData.count || totalFlashcards.length
|
||||
|
||||
const quizItems = generateQuizItemsFromFlashcards(dueFlashcards)
|
||||
|
||||
console.log('📊 資料統計:', {
|
||||
dueFlashcards: dueFlashcards.length,
|
||||
totalFlashcards: totalFlashcards.length,
|
||||
totalCount: totalCount,
|
||||
quizItems: quizItems.length
|
||||
})
|
||||
|
||||
dispatch({
|
||||
type: 'LOAD_FLASHCARDS_SUCCESS',
|
||||
payload: { flashcards, quizItems }
|
||||
payload: {
|
||||
flashcards: dueFlashcards,
|
||||
quizItems,
|
||||
totalCount: totalCount
|
||||
}
|
||||
})
|
||||
|
||||
console.log('✅ 成功載入', flashcards.length, '張詞卡')
|
||||
console.log('✅ 成功載入', dueFlashcards.length, '張待複習詞卡 / 總共', totalCount, '張詞卡')
|
||||
console.log('🎯 生成', quizItems.length, '個測驗項目')
|
||||
} else {
|
||||
console.error('❌ API 調用失敗:', {
|
||||
dueError: dueResponse.error,
|
||||
totalError: totalResponse.error,
|
||||
dueSuccess: dueResponse.success,
|
||||
totalSuccess: totalResponse.success
|
||||
})
|
||||
|
||||
// 檢查是否為認證錯誤
|
||||
const authError = dueResponse.error?.includes('登入已過期') ||
|
||||
totalResponse.error?.includes('登入已過期') ||
|
||||
dueResponse.error?.includes('認證') ||
|
||||
totalResponse.error?.includes('認證')
|
||||
|
||||
dispatch({
|
||||
type: 'LOAD_FLASHCARDS_ERROR',
|
||||
payload: { error: response.error || '載入詞卡失敗' }
|
||||
payload: {
|
||||
error: authError ?
|
||||
'登入已過期,請重新登入' :
|
||||
(dueResponse.error || totalResponse.error || '載入詞卡失敗')
|
||||
}
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ API 調用異常:', error)
|
||||
|
||||
// 檢查是否為認證錯誤
|
||||
const errorMessage = error instanceof Error ? error.message : '載入詞卡失敗'
|
||||
const isAuthError = errorMessage.includes('登入已過期') ||
|
||||
errorMessage.includes('認證') ||
|
||||
errorMessage.includes('Unauthorized')
|
||||
|
||||
dispatch({
|
||||
type: 'LOAD_FLASHCARDS_ERROR',
|
||||
payload: { error: error instanceof Error ? error.message : '載入詞卡失敗' }
|
||||
payload: {
|
||||
error: isAuthError ?
|
||||
'登入已過期,請重新登入' :
|
||||
errorMessage
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -467,6 +536,7 @@ export function useReviewSession() {
|
|||
isLoading,
|
||||
error,
|
||||
flashcards,
|
||||
totalFlashcardsCount,
|
||||
|
||||
// 計算屬性
|
||||
totalQuizItems: quizItems.length,
|
||||
|
|
|
|||
|
|
@ -66,6 +66,12 @@ export class ApiClient {
|
|||
const apiError = await createApiErrorFromResponse(response, context)
|
||||
lastError = apiError
|
||||
|
||||
// 處理認證錯誤 - 自動清除過期token
|
||||
if (response.status === 401) {
|
||||
localStorage.removeItem('auth_token')
|
||||
console.log('🔒 認證過期,已清除token')
|
||||
}
|
||||
|
||||
// 如果不可重試,直接返回錯誤
|
||||
if (attempt === retries || !this.isRetryableStatus(response.status)) {
|
||||
return createErrorResponse(apiError)
|
||||
|
|
@ -138,12 +144,11 @@ export class ApiClient {
|
|||
private getDefaultHeaders(): Record<string, string> {
|
||||
const headers: Record<string, string> = {}
|
||||
|
||||
// 開發階段:不添加認證Token
|
||||
// TODO: 生產環境需要添加認證邏輯
|
||||
// const token = localStorage.getItem('auth_token')
|
||||
// if (token) {
|
||||
// headers.Authorization = `Bearer ${token}`
|
||||
// }
|
||||
// 添加認證Token(如果存在)
|
||||
const token = localStorage.getItem('auth_token')
|
||||
if (token) {
|
||||
headers.Authorization = `Bearer ${token}`
|
||||
}
|
||||
|
||||
return headers
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue