Remove user-app v1 and ship pending order, auth, and app fixes.
Drop the duplicate customer app from the monorepo and keep user-app-v2 as the only app; include order pricing, admin cart, OTP/SMS, and dashboard cache updates. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -15,6 +15,7 @@ export function useCachedReads() {
|
||||
myLedgerEntries: (o?: FetchOptions) => cache.fetch(KEYS.ledger, api.getMyLedgerEntries, o),
|
||||
goldPrice: (o?: FetchOptions) => cache.fetch(KEYS.goldPrice, () => api.getLatestGoldPrice(), o),
|
||||
homeSlider: (o?: FetchOptions) => cache.fetch(KEYS.homeSlider, api.getHomeSlider, o),
|
||||
customButton: (o?: FetchOptions) => cache.fetch(KEYS.customButton, api.getCustomButton, o),
|
||||
peek: cache.peek,
|
||||
invalidate: cache.invalidate,
|
||||
}
|
||||
|
||||
@@ -303,6 +303,13 @@ export interface RegisterBody {
|
||||
city: string;
|
||||
}
|
||||
|
||||
/** Admin custom button from ثابتها — shown on the profile dashboard when enabled. */
|
||||
export interface CustomButton {
|
||||
enabled: boolean;
|
||||
title: string;
|
||||
link: string;
|
||||
}
|
||||
|
||||
/** One admin-uploaded home banner slide — raw shape as saved by the admin
|
||||
* panel's manage/Const.vue "اسلایدر موبایل" tab (image + optional link). */
|
||||
export interface AdminSlide {
|
||||
@@ -567,6 +574,8 @@ export const api = {
|
||||
/** GET /home/slider — public, no auth. Empty array when the admin hasn't
|
||||
* configured any slides (the caller falls back to a derived hero). */
|
||||
getHomeSlider: () => request<AdminSlide[]>('/home/slider'),
|
||||
/** GET /home/custom-button — public. Profile dashboard row when enabled. */
|
||||
getCustomButton: () => request<CustomButton>('/home/custom-button'),
|
||||
|
||||
// ── D21: sales-expert screens ────────────────────────────────────────
|
||||
// All three scope themselves on the AdminUser id inside the token; no id
|
||||
|
||||
@@ -43,6 +43,7 @@ export const KEYS = {
|
||||
catalog: 'catalog',
|
||||
goldPrice: 'goldPrice',
|
||||
homeSlider: 'homeSlider',
|
||||
customButton: 'customButton',
|
||||
profile: 'me:profile',
|
||||
orders: 'me:orders',
|
||||
balance: 'me:balance',
|
||||
|
||||
@@ -149,6 +149,23 @@
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
v-if="showCustomButton"
|
||||
type="button"
|
||||
class="dash__row"
|
||||
@click="openCustomButton"
|
||||
>
|
||||
<span class="dash__rowIcon" aria-hidden="true">
|
||||
<Icon icon="vuesax-linear:link-2" size="18" />
|
||||
</span>
|
||||
<span class="dash__rowText">
|
||||
<b>{{ customButton!.title.trim() }}</b>
|
||||
</span>
|
||||
<span class="dash__rowChevron" aria-hidden="true">
|
||||
<Icon icon="vuesax-linear:arrow-left-1" size="16" />
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<button type="button" class="dash__row" @click="goToDetails(2)">
|
||||
<span class="dash__rowIcon" aria-hidden="true">
|
||||
<Icon icon="vuesax-linear:user" size="18" />
|
||||
@@ -276,7 +293,7 @@ import RateStrip from '../../components/home/RateStrip.vue'
|
||||
import IslandSheet from '../../components/ui/IslandSheet.vue'
|
||||
import { price } from '../../utils/filters'
|
||||
import { useSessionStore } from '../../stores/session'
|
||||
import { api, type CustomerDetail, type GoldPriceQuote } from '../../api/client'
|
||||
import { api, type CustomButton, type CustomerDetail, type GoldPriceQuote } from '../../api/client'
|
||||
import { useCachedReads } from '../../api/cachedReads'
|
||||
import { KEYS } from '../../stores/queryCache'
|
||||
|
||||
@@ -286,9 +303,20 @@ const reads = useCachedReads()
|
||||
|
||||
const customer = ref<CustomerDetail | null>(reads.peek<CustomerDetail>(KEYS.profile) ?? null)
|
||||
const goldPrice = ref<GoldPriceQuote | null>(reads.peek<GoldPriceQuote>(KEYS.goldPrice) ?? null)
|
||||
const customButton = ref<CustomButton | null>(reads.peek<CustomButton>(KEYS.customButton) ?? null)
|
||||
const refreshing = ref(false)
|
||||
const profileError = ref(false)
|
||||
|
||||
const showCustomButton = computed(() => {
|
||||
const btn = customButton.value
|
||||
return Boolean(btn?.enabled && btn.title.trim() && btn.link.trim())
|
||||
})
|
||||
|
||||
function openCustomButton() {
|
||||
const link = customButton.value?.link?.trim()
|
||||
if (link) window.open(link, '_blank', 'noopener,noreferrer')
|
||||
}
|
||||
|
||||
// The ledger stores the gram position as a signed figure — positive means the
|
||||
// customer owes gold. Labelled the same way AccountStrip does on the home
|
||||
// screen so the two never disagree.
|
||||
@@ -346,7 +374,7 @@ async function loadDashboard() {
|
||||
// ran and PullRefresh stayed stuck in its loading state — a working pull
|
||||
// gesture never dismissed it again until a full page reload.
|
||||
profileError.value = false
|
||||
const [me, price] = await Promise.allSettled([
|
||||
const [me, price, btn] = await Promise.allSettled([
|
||||
// The balance on this tab is the one figure read fresh on every visit;
|
||||
// the cached copy only fills the screen until it arrives.
|
||||
session.isLoggedIn ? reads.me({ force: true }) : Promise.resolve(null),
|
||||
@@ -356,6 +384,7 @@ async function loadDashboard() {
|
||||
// app launch. With both reads always fresh the parameter had nothing left
|
||||
// to control, so it is gone.
|
||||
reads.goldPrice({ force: true }),
|
||||
reads.customButton({ force: true }),
|
||||
])
|
||||
if (me.status === 'fulfilled') {
|
||||
customer.value = me.value
|
||||
@@ -365,6 +394,7 @@ async function loadDashboard() {
|
||||
console.log(me.reason)
|
||||
}
|
||||
goldPrice.value = price.status === 'fulfilled' ? price.value : null
|
||||
if (btn.status === 'fulfilled') customButton.value = btn.value
|
||||
}
|
||||
|
||||
async function onRefresh() {
|
||||
|
||||
Reference in New Issue
Block a user