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:
s.esmaeili
2026-09-16 01:15:16 +03:30
parent 0e5f9ccd7b
commit e756fbaf85
30 changed files with 890 additions and 365 deletions
+65 -9
View File
@@ -1,14 +1,20 @@
<!-- Ported from legacy henza_pwa/src/components/dropDown.vue -->
<!--
Ported from legacy henza_pwa/src/components/dropDown.vue, restyled into the
redesigned design language (see components/home/* and HomeView) — previously
raw Tailwind utility classes with legacy tokens (text-primary,
border-lightGray/20), the one accordion in the app that didn't match the
LESS-token look every other surface uses.
-->
<template>
<div :class="`rounded-[5px] border border-solid text-primary px-[15px] py-[15px] ${show ? 'border-secondary' : 'border-lightGray/20'}`">
<div class="flex w-full justify-between cursor-pointer" @click="show = !show">
<h5 class="flex-1 m-0">
<div class="dd" :class="{ 'is-open': show }">
<button type="button" class="dd__head" @click="show = !show">
<h3 class="dd__title">
<slot name="text">{{ text }}</slot>
</h5>
<Icon icon="vuesax-linear:arrow-down" :class="`transition-all duration-200 ease-linear ${show ? 'rotate-180' : undefined}`" size="25" />
</div>
</h3>
<Icon icon="vuesax-linear:arrow-down" class="dd__chevron" size="18" />
</button>
<transition name="drop" mode="out-in" appear>
<div class="w-full" style="overflow: hidden" v-if="show">
<div class="dd__body" v-if="show">
<slot />
</div>
</transition>
@@ -23,6 +29,56 @@ withDefaults(defineProps<{ text?: string }>(), {})
const show = ref(false)
</script>
<style scoped lang="less">
.dd {
padding: 15px;
border: 1px solid @hairline;
border-radius: 16px;
background: @canvas;
}
.dd.is-open {
border-color: @wineSoft;
}
.dd__head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
width: 100%;
border: 0;
padding: 0;
cursor: pointer;
background: none;
font-family: inherit;
text-align: start;
}
.dd__title {
margin: 0;
font-size: 13px;
font-weight: 700;
color: @ink;
}
.dd__chevron {
flex: 0 0 auto;
color: @inkFaint;
transition: transform 0.2s ease;
}
.dd.is-open .dd__chevron {
transform: rotate(180deg);
color: @wine;
}
.dd__body {
overflow: hidden;
padding-top: 15px;
}
</style>
<style lang="less">
.drop-enter-active {
animation: drop 0.2s cubic-bezier(0.33, 1, 0.68, 1);
@@ -35,7 +91,7 @@ const show = ref(false)
max-height: 0;
}
100% {
max-height: 120px;
max-height: 240px;
}
}
</style>
+98 -134
View File
@@ -1,155 +1,119 @@
<!-- Ported from legacy henza_pwa/src/components/ui/navigation.vue -->
<template>
<nav class="fixed bottom-0 w-full z-[10]" v-show="nav.showNav">
<div class="flex flex-row-reverse justify-around items-center absolute w-full h-[50px] z-[2] bottom-0">
<template v-for="item in nav.navBarRoutes" :key="item.index">
<router-link :to="{ name: item.name }" class="relative h-[50px]" @click="setLastPositionX(item)">
<div class="nav-item">
<Icon
:icon="item.icon"
class="cursor-pointer"
:style="{ transform: nav.activeLink === item.index ? `translateY(50px)` : undefined }"
size="25"
/>
</div>
</router-link>
</template>
</div>
<div class="active-nav-item absolute" :style="{ top: output[0] + 'px', left: output[1] + 'px' }">
<Icon v-if="activeItem?.icon === 'vuesax-linear:user'" icon="vuesax-linear:user" class="cursor-pointer" size="25" />
<Icon
v-if="activeItem?.icon === 'vuesax-linear:bag-happy'"
icon="vuesax-linear:bag-happy"
class="cursor-pointer"
size="25"
/>
<Icon
v-if="activeItem?.icon === 'vuesax-linear:search-normal-1'"
icon="vuesax-linear:search-normal-1"
class="cursor-pointer"
size="25"
/>
<Icon
v-if="activeItem?.icon === 'vuesax-linear:home-2'"
icon="vuesax-linear:home-2"
class="cursor-pointer"
size="25"
/>
</div>
<!--
Redesigned bottom nav — replaces the old GSAP SVG-notch bar (4 tabs: user,
cart, search, home) with a flat pill-bar of 5 tabs (Home, Search, Likes,
Notifications, Profile), each showing icon + label, and a single sliding
indicator behind the active tab instead of a cut-out notch.
<svg class="h-[50px] w-full">
<path id="nav" class="from-path" :d="shape" />
</svg>
windowData/SVG path math from the old version is gone — the indicator is
now pure CSS, positioned with a transform driven by the active tab's index,
so it needs no measurement of the container width.
-->
<template>
<nav class="tabbar" v-show="nav.showNav">
<div class="tabbar__inner" :style="{ '--count': nav.navBarRoutes.length }">
<div
class="tabbar__indicator"
:style="{ transform: `translateX(${indicatorOffset}%)` }"
aria-hidden="true"
/>
<router-link
v-for="item in nav.navBarRoutes"
:key="item.index"
:to="{ name: item.name }"
class="tabbar__item"
:class="{ 'is-active': nav.activeLink === item.index }"
>
<Icon :icon="item.icon" size="21" />
<span class="tabbar__label">{{ item.label }}</span>
</router-link>
</div>
</nav>
</template>
<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import { gsap } from 'gsap'
import { TransitionPresets, useTransition } from '@vueuse/core'
import { computed } from 'vue'
import Icon from '../icon/Icon.vue'
import { useNavStore } from '../../stores/navStore'
import { useWindowDataStore } from '../../stores/windowDataStore'
const nav = useNavStore()
const windowData = useWindowDataStore()
const shape = ref('')
const centershape = [95, 45]
const width = computed(() => windowData.width)
const centerOfCentershape = computed(() => {
return (width.value / nav.navBarRoutes.length) * nav.activeLink - width.value / (nav.navBarRoutes.length * 2)
})
const draw = () => {
const drowData = {
cornnerCurve: 10,
height: 50,
centerTopCurve: 20,
centerBottpmCurve: 32,
centershapeWidth: centershape[0],
centershapeHeight: centershape[1],
startOfCentershape: centerOfCentershape.value - centershape[0] / 2,
endOfCentershape: centerOfCentershape.value + centershape[0] / 2,
}
return `
M0,0
H${drowData.startOfCentershape}
C${drowData.startOfCentershape + drowData.centerTopCurve}, 0, ${centerOfCentershape.value - drowData.centerBottpmCurve}, ${drowData.centershapeHeight}, ${centerOfCentershape.value}, ${drowData.centershapeHeight}
C${centerOfCentershape.value + drowData.centerBottpmCurve},${drowData.centershapeHeight},${drowData.endOfCentershape - drowData.centerTopCurve},0,${drowData.endOfCentershape},0
H${width.value}
V50
H0
Z`
}
watch(width, (val) => {
if (val > 0) {
shape.value = draw()
}
})
watch(
() => nav.$state,
() => {
if (!shape.value) {
shape.value = draw()
} else {
gsap.to('.from-path', { duration: 0.3, attr: { d: draw() } })
}
},
{ deep: true },
)
shape.value = draw()
let lastPositionX = width.value
const activeItem = computed(() => {
const item = nav.navBarRoutes.find((x) => x.index === nav.activeLink)
setActiveIcon()
return item
})
// Legacy called this with an `item` argument that the function body never
// used (plain JS silently ignores extra args); kept as an unused optional
// param so behavior stays identical under TypeScript.
const setLastPositionX = (_item?: unknown) => {
lastPositionX = centerOfCentershape.value - centershape[0] / 2 + 25
}
const setActiveIcon = () => {
const positionX = centerOfCentershape.value - centershape[0] / 2 + 25
baseVector.value = [50, positionX + (lastPositionX - positionX) / 2]
setTimeout(() => {
baseVector.value = [-20, positionX]
}, 150)
}
const baseVector = ref([0, 0])
const output = useTransition(baseVector, {
duration: 150,
transition: TransitionPresets.easeInOutCubic,
})
// Index is 1-based; the indicator sits in tab slot (activeLink - 1) out of N.
// The whole app runs `direction: rtl` (see App.vue), which flips the grid's
// visual column order but NOT a physical translateX — so slot 0 renders on
// the right, and moving right needs a NEGATIVE offset here.
const indicatorOffset = computed(() => -(nav.activeLink - 1) * 100)
</script>
<style scoped lang="less">
#nav {
opacity: 0.4;
fill: #dddddd;
.tabbar {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 10;
padding: 10px 14px calc(10px + env(safe-area-inset-bottom, 0px));
background: transparent;
}
.nav-item {
.tabbar__inner {
position: relative;
display: grid;
grid-template-columns: repeat(var(--count), 1fr);
max-width: 420px;
margin: 0 auto;
padding: 6px;
border-radius: 22px;
background: @canvas;
border: 1px solid @hairline;
box-shadow: 0 10px 28px -12px rgba(20, 22, 26, 0.18);
}
.tabbar__indicator {
position: absolute;
inset: 6px 6px 6px auto;
width: calc((100% - 12px) / var(--count));
border-radius: 16px;
background: @wineWash;
transition: transform 0.35s cubic-bezier(0.65, 0, 0.35, 1);
will-change: transform;
}
.tabbar__item {
position: relative;
z-index: 1;
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
width: 45px;
height: 45px;
justify-content: center;
gap: 2px;
padding: 8px 4px;
border-radius: 16px;
color: @inkFaint;
text-decoration: none;
transition: color 0.25s ease, transform 0.25s cubic-bezier(0.65, 0, 0.35, 1.4);
}
.active-nav-item {
.nav-item;
background: @primary;
color: @white;
border-radius: 100%;
.tabbar__item :deep(svg) {
transition: transform 0.3s cubic-bezier(0.65, 0, 0.35, 1.4);
}
.tabbar__item.is-active {
color: @wine;
transform: translateY(-2px);
}
.tabbar__item.is-active :deep(svg) {
transform: scale(1.08);
}
.tabbar__item:active {
transform: scale(0.94);
}
.tabbar__label {
font-size: 10px;
font-weight: 600;
line-height: 1;
opacity: 0.9;
}
</style>
+3 -1
View File
@@ -52,8 +52,10 @@ function onBack() {
<style scoped lang="less">
.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);