昨天用「插座多」、「不限時」等條件縮小了選店範圍。找到想去的店之後,我希望下次打開 WorkCafe,還能找回它。
今天就把前面做好的 Google 登入接到收藏功能。先從卡片上的收藏、取消收藏做起,再確認重新整理後,狀態有沒有留下來。
開始這一步之前,首頁已經能瀏覽、搜尋與篩選店家,也有 Google 登入。這次在現有 CafeCard 加上收藏按鈕,先把儲存與讀取接好;獨立的收藏清單頁留到後面再做。
收藏先使用首頁 Firestore 裡的店家,以 Cafe.id 識別。Places 單店測試頁仍維持獨立,測試用 ID 不加入收藏。
沒登入的人仍然可以使用地圖與篩選。只有按下收藏時,才需要登入。
店家資料繼續由 Laravel 讀取 Firestore,再透過 Inertia 傳給 React。收藏則沿用目前的 Firebase Auth,由前端 Firebase Web SDK 讀寫個人的收藏路徑:
users/{uid}/favorites/{cafeId}
這裡的 uid 使用 Firebase Auth 的使用者 ID,cafeId 使用首頁 Cafe.id,不是 Google Place ID。
收藏文件只記錄關聯與建立時間:
| 欄位或位置 | 用途 |
|---|---|
| users/{uid} | 區分使用者 |
| favorites/{cafeId} | 以店家 ID 作為文件 ID,同一間店只留一筆 |
| cafeId:string | 與文件 ID 相同 |
| createdAt:Timestamp | 以 serverTimestamp() 記錄建立時間 |
收藏文件只記住店家 ID。店名、地址和工作條件仍讀取當次載入的店家資料,避免在收藏裡多存一份日後需要同步的內容。
這個路徑不需要先建立 users 個人資料文件。第一次成功收藏時,favorites 子集合與文件就會出現,不必先到 Console 手動新增。
請規劃現有 Laravel+Inertia+React 專案的最小會員收藏功能,
先列計畫,不修改程式或雲端設定。
閱讀 docs/ CURRENT 規格與 README,檢查:
AuthContext、UserAuthButton、lib/firebase.ts、CafeCard、Home、
FirestoreCafeService、店家型別,以及現有 Firestore Rules 與測試設定。
檔案若已更名,找出對應實作。
目前首頁透過 Laravel+Inertia 讀取 Firestore 店家,
前端已有 Firebase Auth Google 登入。
本次新增收藏是明確的新範圍;若 CURRENT 文件仍排除收藏,
指出需同步更新的範圍,不把舊規格當成已實作。
採用以下分工:
- 店家讀取維持原流程。
- 收藏使用同一個 Firebase app 的 Web SDK,
連到原本專案的 (default) Firestore,以 Firebase Auth 身分存取。
- 路徑 users/{uid}/favorites/{cafeId}。
- uid 取自目前登入者;cafeId 是首頁 Cafe.id。
- 文件只存 cafeId 與 createdAt,不複製 Cafe 或 Places 回應。
- 不使用 importer JSON 或 Laravel 服務帳號替瀏覽器授權。
列出:
1. 必要修改檔案與共用收藏狀態放置方式。
2. 未登入點收藏、登入成功後接續收藏、取消登入的流程。
3. 載入、寫入中、成功與失敗的 UI 狀態。
4. Firestore Rules、文件欄位驗證及帳號隔離測試。
5. 登出或換帳號時,如何清掉前一人的收藏,
並避免較晚回來的舊請求污染新帳號畫面。
6. 新增、取消、重複點擊、重新整理與失敗測試方式。
收藏先放在既有 CafeCard。
不新增 Bottom Sheet、收藏清單頁或即時監聽;
不把 Places 單店測試結果加入收藏,不改搜尋與篩選規則。
若讀不到目前雲端 Rules,列為待核對,不假設它已允許或拒絕存取。
列完計畫先停下,不輸出金鑰、token、實際 Project ID 或個人資料。
看完計畫後,先核對下面的存取規則,再交給 Agent 一起實作與測試。雲端規則等測試完成後才發布。
收藏由瀏覽器直接讀寫 Firestore,因此要用 Security Rules 確認「登入者是不是這份收藏的主人」。原本 Laravel 讀取店家的服務帳號仍使用 IAM 權限,沿用既有設定。Firestore 規則與身分驗證
收藏規則需要包含:
先到 Firebase Console 的「Firestore Database → (default) → 規則」查看並備份目前內容,讓 Agent 依這份規則準備修改。這裡也要檢查有沒有全域開放的設定:同一個請求若符合多段規則,只要其中一段 allow 放行就能存取,新增嚴格規則不會抵銷原本的開放條件。規則比對方式
接下來由 Agent 準備規則,並用 Firebase Emulator(本機模擬器)測試未登入、本人、其他帳號與不合法欄位。若測試環境還沒設定好,先補齊設定再跑。Console 能手動新增文件,並不能證明網站使用者也有相同權限。
依剛才確認的計畫實作最小會員收藏,並準備 Firestore Rules。
沿用既有 Firebase app、Google 登入與 (default) Firestore。
首頁 Cafe 資料仍由 Laravel+Inertia 提供。
收藏只寫 users/{目前登入 uid}/favorites/{Cafe.id},
文件只存 cafeId 與 createdAt。
新增時使用 serverTimestamp();已存在就保留,不覆寫原建立時間。
取消收藏刪除同一路徑。以固定文件 ID 避免重複文件,
必要時使用 transaction 處理先讀後寫的一致性。
Security Rules 需驗證本人 uid、允許欄位、型別、
文件 ID 與 cafeId 相同、建立時間及 cafes 文件存在。
允許本人讀取、新增與刪除,不開放更新。
檢查既有規則是否有更寬的 allow,不破壞原本必要規則。
先提供規則差異與 Emulator 測試結果,不自動發布雲端規則。
在 CafeCard 加入收藏按鈕,避免點擊時觸發卡片選取。
未登入時顯示同頁登入提示,保留搜尋、篩選和地圖狀態。
只記住使用者這次點選的 cafeId:
登入成功後確認 Auth 身分,再完成那一次收藏;
登入取消或失敗就清除待辦,不延後到下一次登入自動執行。
請檢查 loginWithGoogle 的錯誤回傳方式,
不能只因 Promise 結束就當作登入成功。
登入初始化與收藏載入時不把未知狀態畫成「未收藏」。
寫入期間禁止同店重複操作;伺服器確認成功才顯示已收藏。
失敗時保留原狀態並提示;讀取失敗不偽裝成空收藏。
離線時顯示未完成或等待連線,不當作已存入。
共用收藏狀態,篩選或切換地圖/列表時維持一致。
登出或換帳號時立即清除前一人的狀態與待處理操作。
所有非同步回應都核對目前 uid,避免舊回應覆蓋新帳號。
重新整理後等 Auth 還原,再讀取該使用者的 favorites。
不新增跨分頁即時同步;先以重新載入確認持久化。
不新增 Bottom Sheet、收藏清單頁或即時監聽,
不把 Places 測試資料存入收藏,不更動店家文件或篩選規則。
必要時同步更新 CURRENT 文件,註明新增範圍。
測試本人與他人權限、取消登入、重複點擊、讀寫失敗、
取消收藏、換帳號與重新整理。
執行 npm run typecheck、npm run build 與相關測試。
列出修改檔案、實際結果、未驗證事項與待發布的規則。
不自動 commit、push 或發布雲端設定。
Agent 完成後,先確認型別檢查、建置及相關測試的結果,再核對規則差異。接著回到原專案的「Firestore Database → (default) → 規則」,合併已測試的修改並按「發布」。
這次收藏沿用 Firebase Web App 設定與使用者登入身分,不需要新增服務帳號 JSON。規則發布後,再開啟本機首頁,進行下面的實際讀寫測試。
如果出現 permission-denied,先核對目前登入帳號、Firestore 專案與資料庫、文件路徑及已發布的規則,不要把整個資料庫改成公開來繞過錯誤。
先登出,在首頁搜尋一間尚未收藏的店,再按它的收藏按鈕。測試時都用同一間,方便對照網站與 Firestore 文件。
預期流程是:
瀏覽店家 → 點收藏 → 同頁登入提示
→ 使用者點 Google 登入 → Auth 確認成功
→ 寫入剛才那間店 → 顯示已收藏
第一次先關掉登入視窗,確認仍留在原畫面,收藏也沒有寫入。接著重新按收藏並完成 Google 登入,確認網站接續收藏的是剛才那間店。
📸 圖片 1|未登入點收藏時的登入提示
登入並完成收藏後,到 Firestore 的 users/{uid}/favorites 查看文件。文件 ID 與 cafeId 應對上剛才那間店,createdAt 是 Timestamp,而不是前端自行格式化的日期字串。伺服器時間戳記
確認同一個 cafeId 只有一份收藏文件,且首頁的 cafes 文件沒有被修改。先保留這筆收藏,接著測試重新整理能不能讀回。
📸 圖片 2|Firestore 收藏文件
等收藏成功後,重新整理首頁。待登入狀態與收藏資料載入完成,再看同一間店是否仍顯示已收藏。若搜尋或篩選條件已重設,重新搜尋店名即可;這次先不做條件記憶。
再切換地圖與列表,或用篩選暫時隱藏那間店後恢復,確認收藏狀態一致。最後才取消收藏,檢查對應文件是否刪除,再重新整理一次,確認沒有自動變回已收藏。
📸 圖片 3-1|收藏前
📸 圖片 3-2|收藏成功
📸 圖片 3-3|重新整理後
失敗情境用 Emulator 或模擬回應測試,讓寫入回傳權限拒絕,確認畫面有錯誤提示且沒有顯示收藏成功。線上規則維持原設定。
另外,瀏覽器切成離線測到的是網路中斷。Firestore 可能等待恢復連線,所以畫面應顯示等待或未完成,不能提前當成已存入。
| 檢查 | 預期結果 |
|---|---|
| 未登入瀏覽 | 地圖、搜尋與篩選可用 |
| 取消登入後再次登入 | 不補做已取消的收藏 |
| 收藏或取消成功 | 圖示與文件狀態一致 |
| 寫入中連續點擊 | 同店不重複送出 |
| 重新整理 | 身分還原後讀回本人收藏 |
| 讀取或寫入失敗 | 顯示錯誤,保留可辨識的未完成狀態 |
| 登出、切換帳號 | 不顯示前一人的收藏 |
| 未登入或跨帳號存取 | Rules 拒絕讀寫 |
| 搜尋、篩選、切換檢視 | 不誤刪收藏、不干擾原本選店行為 |
篩選幫忙找到合適的店,收藏則把這次的選擇留給下次。除了愛心有沒有亮起來,更要確認重新整理後讀得回來、取消時刪得掉,換帳號也不會看到別人的狀態。
先把這些流程測清楚,之後再整理成獨立的收藏列表。