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:
mahmood
2026-10-28 16:22:32 +03:30
co-authored by Cursor
parent bc881a0a8a
commit 520454481b
367 changed files with 335 additions and 28536 deletions
+1
View File
@@ -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,
}
+9
View File
@@ -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() {