user-app: extend the redesign across the account screens

Phase 2, completing the app-wide rollout of the home/product-detail design
language (LESS colour tokens, BEM class names, hairline-bordered cards,
SectionHead, dashed empty states) onto the account side: dashboard, orders,
transactions, profile, login and register.

Reuse over re-implementation: the dashboard's live rate block is now
components/home/RateStrip.vue rather than a second hand-built strip, and
every screen uses components/ui/PageBar.vue for its chrome.

OrderCard and OrderItemCard are restyled to match. OrderItemCard drops its
always-empty note row: our OrderItem schema has no note field (legacy's
per-item note was a real column there; ours is not), so it could only ever
print a dash.

OrdersView swaps ant Tabs for the same scrolling chip rail the rest of the
redesign uses, and its full-screen Modal with hand-written CSS overrides for
a bottom Drawer, matching how every other detail surface opens.

Deleted AppHeader.vue, orphaned once every screen moved to PageBar. Its
shutter animation was driven by transitionFlag, which nothing reads any
more — the router afterEach comment claiming otherwise is corrected rather
than left asserting something untrue, and the now-inert half is flagged in
place. ScrollFetchSearch loses its isFav prop: it gated two empty-state
branches that rendered the identical image, and its only caller
(FavoritesView) now renders its own.

Behaviour is preserved throughout. Orders still reads GET
/customers/:id/orders and filters client-side; transactions still reads
GET /customers/:id/ledger-entries with legacy's CREDIT/DEBIT colour meaning
intact; profile stays read-only over real fields; login keeps the real
customer lookup plus the documented OTP stub; register stays stubbed with
copy that does not claim an account was created.

Verified in a real browser against the running API and native database:
dashboard 4 tiles with a real balance, orders 1 real order card, profile 4
real spec rows, transactions the correct empty state for a customer with no
ledger rows, and login/register confirmed with the session cleared (the
guard bounces a signed-in user away from /login, which is why they needed a
separate pass). No console errors, no horizontal overflow. vue-tsc and vite
build both clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
s.esmaeili
2026-09-15 01:34:30 +03:30
co-authored by Claude Opus 5
parent cc62975dd0
commit 0e5f9ccd7b
11 changed files with 1853 additions and 714 deletions
+145 -55
View File
@@ -1,78 +1,84 @@
<!--
Ported from legacy henza_pwa/src/views/user/LoginRegister/components/Register.vue.
Ported from legacy henza_pwa/src/views/user/LoginRegister/components/Register.vue,
restyled into the redesigned design language (see components/home/* and
HomeView).
DROPPED as confirmed-dead code (not "possibly dead" — verified unreachable):
legacy rendered a whole cascading country->province->city picker
DROPPED as confirmed-dead code (verified unreachable, not merely "possibly
dead"): legacy rendered a whole cascading country->province->city picker
(WorkActionSheet + selectCityArrays + getCities() against POST /cities/list)
alongside a plain "شهر" text <Input>. The only element that ever opened the
picker (`#citybtn`) was itself commented out in legacy's own template, so
the picker was unreachable UI even in the original app; the visible form
uses the plain text input. That whole complex, and the parallel unused
`userFormModel`/`defaultUserFormModel` objects (the template binds
`formModel.*`, a *different*, separately-declared empty reactive object —
`userFormModel` is never referenced anywhere), are not ported.
picker (`#citybtn`) was itself commented out in legacy's own template, so the
picker was unreachable UI even in the original app; the visible form uses the
plain text input. That whole complex, and the parallel unused
`userFormModel`/`defaultUserFormModel` objects, are not ported.
STUBBED, not real: POST /major/register — apps/api has no create-customer
endpoint. Submitting shows legacy's exact success dialog without actually
persisting anything.
STUBBED, not real, and unchanged from the previous version: legacy's
POST /major/register — apps/api has no create-customer endpoint. Submitting
shows legacy's exact success message without persisting anything. The copy
says the account awaits review, which is what legacy said too, so the screen
does not claim an account exists when none was created.
-->
<template>
<div>
<div class="fixed w-full">
<img class="w-full absolute left-0 right-0" :src="pattern1" alt="" />
<img class="absolute w-full left-0 right-0" :src="pattern2" alt="" />
</div>
<div class="fixed top-0 z-10 w-full">
<AppHeader class="bg-white shadowHeader">
<div class="flex justify-between items-center">
<Icon icon="vuesax-linear:arrow-right-1" class="cursor-pointer" size="25" @click="router.push({ name: 'home' })" />
<h3 class="t bold mb-0">ثبت نام</h3>
<div></div>
</div>
</AppHeader>
</div>
<div class="relative w-full py-16 px-5">
<div class="mt-14">
<Spinner :spinning="profileData.loading" tip="در حال ثبت اطلاعات">
<Form :model="formModel" layout="vertical" @finish="register" class="w-full">
<FormItem style="font-size: 16px" name="fname" label="نام" :rules="required('نام')">
<div class="auth">
<PageBar title="ثبت نام" back-to="home" />
<Spinner :spinning="profileData.loading" tip="در حال ثبت اطلاعات">
<container class="auth__body">
<section class="auth__intro">
<div class="auth__mark" aria-hidden="true">
<Icon icon="vuesax-linear:user-add" size="28" />
</div>
<h2 class="auth__title">ساخت حساب کاربری</h2>
<p class="auth__sub">اطلاعات فروشگاه خود را وارد کنید</p>
</section>
<section class="auth__card">
<Form :model="formModel" layout="vertical" @finish="register" class="auth__form">
<FormItem name="fname" label="نام" :rules="required('نام')">
<Input v-model:value="formModel.fname" />
</FormItem>
<FormItem style="font-size: 16px" name="lname" label="نام خانوادگی" :rules="required('نام خانوادگی')">
<FormItem name="lname" label="نام خانوادگی" :rules="required('نام خانوادگی')">
<Input v-model:value="formModel.lname" />
</FormItem>
<FormItem style="font-size: 16px" name="mobile" label="شماره موبایل" :rules="[mobileRule('شماره موبایل'), required('شماره موبایل')]">
<FormItem
name="mobile"
label="شماره موبایل"
:rules="[mobileRule('شماره موبایل'), required('شماره موبایل')]"
>
<Input v-model:value="formModel.mobile" type="number" />
</FormItem>
<FormItem style="font-size: 16px" name="store_name" label="نام فروشگاه" :rules="required('نام فروشگاه')">
<FormItem name="store_name" label="نام فروشگاه" :rules="required('نام فروشگاه')">
<Input v-model:value="formModel.store_name" />
</FormItem>
<FormItem style="font-size: 16px" name="phone" label="تلفن فروشگاه" :rules="[required('تلفن فروشگاه')]">
<FormItem name="phone" label="تلفن فروشگاه" :rules="[required('تلفن فروشگاه')]">
<Input v-model:value="formModel.phone" type="number" />
</FormItem>
<FormItem style="font-size: 16px" name="address" label="آدرس پستی" :rules="required('آدرس')">
<Textarea v-model:value="formModel.address" />
<FormItem name="address" label="آدرس پستی" :rules="required('آدرس')">
<Textarea v-model:value="formModel.address" :rows="3" />
</FormItem>
<FormItem name="city" label="شهر" :rules="required('شهر')">
<Input v-model:value="formModel.city" />
</FormItem>
<Button type="primary" class="w-full max-w-[768px] m-auto" style="height: 42px" html-type="submit">
<span style="font-size: 18px">ثبت اطلاعات</span>
</Button>
<button type="submit" class="auth__cta">ثبت اطلاعات</button>
</Form>
</Spinner>
<div class="flex justify-center mt-10">
<div>اگر حساب کاربری دارید از این قسمت</div>
<div class="pr-1 text-primary font-bold" @click="emit('register', false)">وارد شوید.</div>
</div>
</div>
</div>
</section>
<p class="auth__switch">
اگر حساب کاربری دارید
<button type="button" @click="emit('register', false)">وارد شوید</button>
</p>
</container>
</Spinner>
<DialogVant :timeOut="-1" v-model:show="registeredInfo" :closeOnClickOverlay="false">
<template v-slot:img>
<img :src="registerDelayImage" alt="" class="opacity-80" />
</template>
<template v-slot:title>
<div class="p-5">ثبت نام شما با موفقیت انجام شد. منتظر تایید اطلاعات ثبت نامی شما توسط کارشناسان بمانید.</div>
<div class="p-5">
ثبت نام شما با موفقیت انجام شد. منتظر تایید اطلاعات ثبت نامی شما توسط کارشناسان بمانید.
</div>
</template>
<template v-slot:button>
<div class="text-center" @click="router.push({ name: 'home' })">متوجه شدم</div>
@@ -84,12 +90,10 @@
<script setup lang="ts">
import { reactive, ref } from 'vue'
import { useRouter } from 'vue-router'
import { Button, Form, FormItem, Input, Textarea } from 'ant-design-vue'
import pattern1 from '../../assets/img/pattern2.svg'
import pattern2 from '../../assets/img/pattern3.svg'
import { Form, FormItem, Input, Textarea } from 'ant-design-vue'
import registerDelayImage from '../../assets/img/registerDelay.svg'
import Icon from '../../components/icon/Icon.vue'
import AppHeader from '../../components/AppHeader.vue'
import PageBar from '../../components/ui/PageBar.vue'
import Spinner from '../../components/Spinner.vue'
import DialogVant from '../../components/DialogVant.vue'
import { required, mobile as mobileRule } from '../../utils/validator'
@@ -121,8 +125,94 @@ const register = async () => {
}
</script>
<style scoped>
.shadowHeader {
box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.05), 0 2px 4px -2px rgb(0 0 0 / 0.05);
<style scoped lang="less">
.auth {
background: @canvas;
min-height: 100vh;
}
.auth__body {
padding-top: 26px;
padding-bottom: 90px;
}
.auth__intro {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
padding-bottom: 24px;
}
.auth__mark {
width: 64px;
height: 64px;
border-radius: 50%;
display: grid;
place-items: center;
color: @wine;
background: @wineWash;
}
.auth__title {
margin: 14px 0 0;
font-size: 16px;
font-weight: 800;
color: @ink;
}
.auth__sub {
margin: 6px 0 0;
font-size: 12.5px;
color: @inkFaint;
}
.auth__card {
padding: 18px 16px 20px;
border: 1px solid @hairline;
border-radius: 20px;
background: @canvasSoft;
}
.auth__form :deep(.ant-form-item-label > label) {
font-size: 12px;
color: @inkMuted;
}
.auth__cta {
width: 100%;
margin-top: 4px;
padding: 13px 18px;
border: 0;
cursor: pointer;
border-radius: 14px;
font-family: inherit;
font-size: 13px;
font-weight: 700;
color: #fff;
background: @ink;
transition: transform 0.15s ease;
}
.auth__cta:active {
transform: scale(0.99);
}
.auth__switch {
margin: 20px 0 0;
text-align: center;
font-size: 12px;
color: @inkMuted;
}
.auth__switch button {
border: 0;
background: none;
cursor: pointer;
padding: 0 3px;
font-family: inherit;
font-size: 12px;
font-weight: 700;
color: @wine;
}
</style>