feat(favorites): keep customer favourites on the server, as legacy did (FAV-01)

Both apps kept favourites in memory only, and the API had no route, so
the 874 favourites imported from legacy major_favorites never showed.

- GET customer/me/favorites, POST customer/me/favorites/toggle
- apps load them on sign-in, clear on sign-out, toggle optimistically
  and roll back on failure

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
s.esmaeili
2026-10-08 10:54:17 +03:30
co-authored by Claude Opus 5.5
parent 5480398a93
commit 5d386bf752
10 changed files with 257 additions and 34 deletions
+8
View File
@@ -336,6 +336,14 @@ export const api = {
getAccountingBalance: () => request<AccountingBalance>('/customer/me/accounting-balance'),
getMyOrders: () => request<OrderDetail[]>('/customer/me/orders'),
getMyLedgerEntries: () => request<LedgerEntrySummary[]>('/customer/me/ledger-entries'),
/** Favourite product ids, newest first — legacy get-favorites (FAV-01). */
getFavorites: () => request<{ ids: string[] }>('/customer/me/favorites'),
/** Legacy add-to-fav: adds or removes; answers the new state. */
toggleFavorite: (productId: string) =>
request<{ favorite: boolean }>('/customer/me/favorites/toggle', {
method: 'POST',
body: JSON.stringify({ productId }),
}),
createOrder: (dto: CreateOrderDto) => request<OrderSummary>('/orders', { method: 'POST', body: JSON.stringify(dto) }),
getLatestGoldPrice: (karatPerMille = 750) => request<GoldPriceQuote>(`/pricing/latest?karatPerMille=${karatPerMille}`),
+50 -17
View File
@@ -1,22 +1,55 @@
// Adapted from legacy henza_pwa/src/Mixins/favorite.ts + the `favorits` slice of
// myDataStore. Legacy persisted favorites server-side via POST /add-to-fav; the
// new API has no favorites endpoint yet, so this is local-only (Pinia state,
// not persisted) until that backend support exists. Flagged — not a silent
// behavior match, a deliberate stand-in.
// myDataStore. Legacy kept favourites on the server (POST add-to-fav /
// get-favorites, table major_favorites); so does this, through
// /customer/me/favorites (FAV-01). It used to be memory-only, so favourites
// vanished on reload and the ones imported from legacy were never shown.
import { defineStore } from 'pinia'
import { ref, watch } from 'vue'
import { api } from '../api/client'
import { useSessionStore } from './session'
export const useFavoritesStore = defineStore('favorites', {
state: () => ({
ids: [] as string[],
}),
actions: {
toggle(id: string) {
const index = this.ids.indexOf(id)
if (index === -1) this.ids.push(id)
else this.ids.splice(index, 1)
export const useFavoritesStore = defineStore('favorites', () => {
const ids = ref<string[]>([])
const session = useSessionStore()
async function load() {
try {
ids.value = (await api.getFavorites()).ids
} catch (e) {
// Offline or a dead session (the client already handles 401): keep what we have.
console.warn('favorites: load failed', e)
}
}
// Loaded for whoever is signed in, cleared on logout — never another user's list.
watch(
() => session.token,
(token) => {
ids.value = []
if (token) void load()
},
isFavorite(id: string) {
return this.ids.includes(id)
},
},
{ immediate: true },
)
/** Flips at once on screen, then confirms with the server (legacy toggled the same way). */
async function toggle(id: string) {
const was = ids.value.includes(id)
ids.value = was ? ids.value.filter((x) => x !== id) : [id, ...ids.value]
try {
const { favorite } = await api.toggleFavorite(id)
const has = ids.value.includes(id)
if (favorite && !has) ids.value = [id, ...ids.value]
if (!favorite && has) ids.value = ids.value.filter((x) => x !== id)
} catch (e) {
// Put it back so the heart never shows a state the server does not hold.
ids.value = was ? [id, ...ids.value.filter((x) => x !== id)] : ids.value.filter((x) => x !== id)
console.warn('favorites: toggle failed', e)
}
}
function isFavorite(id: string) {
return ids.value.includes(id)
}
return { ids, load, toggle, isFavorite }
})