Redesign orders/cart/search flows and fix RTL/auth edge cases in user-app
- Fix silent redirect-to-home for unauthenticated users on protected routes; now redirects to login with a return path - Restyle OrderCard, SearchInput, Filters, DropDown, and product card image sizing/centering to match the app's LESS-token design language - Fix ant-design Slider handle placement/drag under RTL and seed a valid default range so handles actually render - Fix chip-row edge padding and top spacing across Orders/Cart/Search lists - Add pull-to-refresh to the dashboard and fix its header/padding to match the fixed PageBar pattern - Remove the floating WhatsApp button from index.html
This commit is contained in:
@@ -48,6 +48,7 @@
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<PullRefresh v-model="refreshing" @refresh="onRefresh">
|
||||
<container class="dash__body">
|
||||
<!-- Signed in: real position from GET /customers/:id -->
|
||||
<template v-if="customer">
|
||||
@@ -117,6 +118,7 @@
|
||||
<RateStrip :quote="goldPrice" />
|
||||
</section>
|
||||
</container>
|
||||
</PullRefresh>
|
||||
|
||||
<DialogVant :timeOut="-1" v-model:show="data.notify" :closeOnClickOverlay="true">
|
||||
<template v-slot:img>
|
||||
@@ -150,6 +152,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { PullRefresh } from 'vant'
|
||||
import Icon from '../../components/icon/Icon.vue'
|
||||
import SectionHead from '../../components/home/SectionHead.vue'
|
||||
import RateStrip from '../../components/home/RateStrip.vue'
|
||||
@@ -165,6 +168,7 @@ const session = useSessionStore()
|
||||
|
||||
const customer = ref<CustomerDetail | null>(null)
|
||||
const goldPrice = ref<GoldPriceQuote | null>(null)
|
||||
const refreshing = ref(false)
|
||||
|
||||
// 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
|
||||
@@ -201,7 +205,7 @@ function logOut() {
|
||||
router.push({ name: 'home' })
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
async function loadDashboard() {
|
||||
if (session.customerId) {
|
||||
customer.value = await api.getCustomer(session.customerId)
|
||||
}
|
||||
@@ -210,7 +214,14 @@ onMounted(async () => {
|
||||
} catch {
|
||||
goldPrice.value = null
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function onRefresh() {
|
||||
await loadDashboard()
|
||||
refreshing.value = false
|
||||
}
|
||||
|
||||
onMounted(loadDashboard)
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
@@ -220,8 +231,10 @@ onMounted(async () => {
|
||||
}
|
||||
|
||||
.dash__bar {
|
||||
position: sticky;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 20;
|
||||
background: rgba(255, 255, 255, 0.88);
|
||||
backdrop-filter: blur(14px);
|
||||
@@ -269,7 +282,7 @@ onMounted(async () => {
|
||||
}
|
||||
|
||||
.dash__body {
|
||||
padding-top: 16px;
|
||||
padding-top: 76px;
|
||||
padding-bottom: 90px;
|
||||
}
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ onMounted(async () => {
|
||||
}
|
||||
|
||||
.favs__body {
|
||||
padding-top: 16px;
|
||||
padding-top: 76px;
|
||||
padding-bottom: 90px;
|
||||
}
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, reactive, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { Form, FormItem, Input } from 'ant-design-vue'
|
||||
import Icon from '../../components/icon/Icon.vue'
|
||||
import PageBar from '../../components/ui/PageBar.vue'
|
||||
@@ -96,6 +96,7 @@ import { api, type CustomerSummary } from '../../api/client'
|
||||
|
||||
const emit = defineEmits<{ (e: 'register', value: boolean): void }>()
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const session = useSessionStore()
|
||||
|
||||
const formModel = reactive({ username: undefined as string | undefined })
|
||||
@@ -152,7 +153,8 @@ const sendToken = async (_otpCode: string) => {
|
||||
body.loading = true
|
||||
if (!matchedCustomer) return
|
||||
session.login(matchedCustomer.id, matchedCustomer.mobile)
|
||||
await router.push({ name: 'home' })
|
||||
const redirect = typeof route.query.redirect === 'string' ? route.query.redirect : undefined
|
||||
await router.push(redirect ?? { name: 'home' })
|
||||
notify.text = 'با موفقیت وارد شدید'
|
||||
notify.show = true
|
||||
} finally {
|
||||
@@ -172,7 +174,7 @@ const btnText = computed(() => (body.step === 1 ? 'دریافت رمز عبور'
|
||||
}
|
||||
|
||||
.auth__body {
|
||||
padding-top: 26px;
|
||||
padding-top: 86px;
|
||||
padding-bottom: 90px;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
<!--
|
||||
New nav destination (bottom nav redesign) — no legacy equivalent and no
|
||||
notifications backend exists in apps/api. Renders the same dashed
|
||||
empty-state pattern used elsewhere (see FavoritesView) rather than
|
||||
fabricating fake notification data.
|
||||
-->
|
||||
<template>
|
||||
<div class="notif">
|
||||
<PageBar title="اعلانها" back-to="home" />
|
||||
|
||||
<container class="notif__body">
|
||||
<section class="notif__empty">
|
||||
<Icon icon="vuesax-linear:notification" size="26" />
|
||||
<p>اعلانی برای نمایش وجود ندارد.</p>
|
||||
</section>
|
||||
</container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Icon from '../../components/icon/Icon.vue'
|
||||
import PageBar from '../../components/ui/PageBar.vue'
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.notif {
|
||||
background: @canvas;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.notif__body {
|
||||
padding-top: 76px;
|
||||
padding-bottom: 90px;
|
||||
}
|
||||
|
||||
.notif__empty {
|
||||
margin-top: 8px;
|
||||
padding: 38px 20px;
|
||||
border: 1px dashed @hairline;
|
||||
border-radius: 18px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
color: @inkFaint;
|
||||
}
|
||||
.notif__empty p {
|
||||
margin: 0;
|
||||
font-size: 12.5px;
|
||||
}
|
||||
</style>
|
||||
@@ -48,9 +48,9 @@
|
||||
</PageBar>
|
||||
|
||||
<Spinner :spinning="loading" tip="درحال دریافت اطلاعات">
|
||||
<PullRefresh v-model="refreshing" @refresh="onRefresh">
|
||||
<container class="ord__body">
|
||||
<template v-if="filteredOrders.length">
|
||||
<SectionHead title="سفارشهای شما" :subtitle="`${filteredOrders.length} سفارش`" />
|
||||
<div class="ord__list">
|
||||
<OrderCard v-for="item in filteredOrders" :key="item.id" :value="item" @view="openDetail" />
|
||||
</div>
|
||||
@@ -61,6 +61,7 @@
|
||||
<p>سفارشی با این مشخصات پیدا نشد.</p>
|
||||
</section>
|
||||
</container>
|
||||
</PullRefresh>
|
||||
</Spinner>
|
||||
|
||||
<Drawer
|
||||
@@ -144,10 +145,10 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { Drawer } from 'ant-design-vue'
|
||||
import { PullRefresh } from 'vant'
|
||||
import Icon from '../../components/icon/Icon.vue'
|
||||
import PageBar from '../../components/ui/PageBar.vue'
|
||||
import Spinner from '../../components/Spinner.vue'
|
||||
import SectionHead from '../../components/home/SectionHead.vue'
|
||||
import SearchInput from '../../components/search/SearchInput.vue'
|
||||
import OrderCard from '../../components/product/OrderCard.vue'
|
||||
import OrderItemCard from '../../components/product/OrderItemCard.vue'
|
||||
@@ -177,6 +178,7 @@ const tabs = [
|
||||
const activeTab = ref('ALL')
|
||||
|
||||
const loading = ref(false)
|
||||
const refreshing = ref(false)
|
||||
const allOrders = ref<OrderDetail[]>([])
|
||||
const searchTerm = ref<string | undefined>(undefined)
|
||||
|
||||
@@ -213,6 +215,11 @@ function openDetail(item: OrderDetail) {
|
||||
viewData.model = true
|
||||
}
|
||||
|
||||
async function onRefresh() {
|
||||
await loadOrders()
|
||||
refreshing.value = false
|
||||
}
|
||||
|
||||
onMounted(loadOrders)
|
||||
</script>
|
||||
|
||||
@@ -225,15 +232,14 @@ onMounted(loadOrders)
|
||||
.ord__barExtra {
|
||||
max-width: 768px;
|
||||
margin: 0 auto;
|
||||
padding: 0 16px 11px;
|
||||
padding: 0 16px 0;
|
||||
}
|
||||
|
||||
.ord__chips {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
overflow-x: auto;
|
||||
margin: 11px -16px 0;
|
||||
padding: 0 16px;
|
||||
margin: 8px 0px 8px -16px;
|
||||
scroll-snap-type: x proximity;
|
||||
}
|
||||
|
||||
@@ -264,13 +270,14 @@ onMounted(loadOrders)
|
||||
}
|
||||
|
||||
.ord__body {
|
||||
padding-top: 16px;
|
||||
padding-top: 160px;
|
||||
padding-bottom: 90px;
|
||||
}
|
||||
|
||||
.ord__list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-top: 16px;
|
||||
gap: 11px;
|
||||
}
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@ onMounted(async () => {
|
||||
}
|
||||
|
||||
.prof__body {
|
||||
padding-top: 20px;
|
||||
padding-top: 80px;
|
||||
padding-bottom: 90px;
|
||||
}
|
||||
|
||||
|
||||
@@ -132,7 +132,7 @@ const register = async () => {
|
||||
}
|
||||
|
||||
.auth__body {
|
||||
padding-top: 26px;
|
||||
padding-top: 86px;
|
||||
padding-bottom: 90px;
|
||||
}
|
||||
|
||||
|
||||
@@ -157,7 +157,7 @@ onMounted(async () => {
|
||||
}
|
||||
|
||||
.tx__body {
|
||||
padding-top: 16px;
|
||||
padding-top: 76px;
|
||||
padding-bottom: 90px;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user