changeof UI

This commit is contained in:
2026-09-13 21:57:58 +03:30
parent 039e15e0dc
commit 6944788b10
59 changed files with 463 additions and 81 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

+10 -1
View File
@@ -259,14 +259,23 @@
padding: 24px;
}
/* صفحات احراز هویت: ستون موبایل در وسط دسکتاپ */
.app-shell--no-nav.app-shell--immersive {
padding: 0;
align-items: center;
justify-content: center;
background: #141210;
}
.app-shell--no-nav.app-shell--immersive .app-layout,
.app-shell--no-nav.app-shell--immersive .app-frame {
max-height: none;
max-width: 430px;
width: min(100%, 430px);
height: 100%;
max-height: 100dvh;
margin-inline: auto;
border-radius: 0;
box-shadow: none;
}
.app-shell--no-nav .app-layout,
+6 -1
View File
@@ -9,6 +9,7 @@ const route = useRoute()
const showNav = computed(() => {
const noNavRoutes = [
'welcome',
'login',
'register',
'otp',
@@ -34,7 +35,11 @@ const isWideGuest = computed(() => {
})
const isImmersiveAuth = computed(
() => route.name === 'login' || route.name === 'register' || route.name === 'otp',
() =>
route.name === 'welcome' ||
route.name === 'login' ||
route.name === 'register' ||
route.name === 'otp',
)
</script>
+6
View File
@@ -4,6 +4,12 @@ import { getCachedUser, isAuthenticated } from '@/services/authService'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/welcome',
name: 'welcome',
component: () => import('@/views/auth/WelcomeView.vue'),
meta: { guest: true, hideNav: true },
},
{
path: '/login',
name: 'login',
+20 -3
View File
@@ -17,6 +17,7 @@ import {
type FieldErrors,
} from '@/utils/formErrors'
import fabricUrl from '@/assets/images/login-fabric.jpg'
import fabricUrl2 from '@/assets/images/login-fabric2.png'
const router = useRouter()
const auth = useAuthStore()
@@ -89,6 +90,7 @@ onMounted(async () => {
<template>
<div class="page login-page">
<img class="login-page__bg" :src="fabricUrl" alt="" />
<img class="login-page__fabric" :src="fabricUrl2" alt="" />
<div class="login-page__scroll">
<div class="login-page__stage">
@@ -219,6 +221,21 @@ onMounted(async () => {
user-select: none;
}
/* پارچه پایین صفحه — پشت باکس لاگین، نه روی آن */
.login-page__fabric {
position: absolute;
left: 0;
right: 0;
bottom: 0;
z-index: 3;
width: 100%;
height: 100%;
object-fit: cover;
object-position: center bottom;
pointer-events: none;
user-select: none;
}
.login-page__scroll {
position: relative;
z-index: 1;
@@ -292,7 +309,7 @@ onMounted(async () => {
padding: 22px 18px 16px;
border: none;
border-radius: 28px;
background: #f4f1ed;
background: #ebe4dc;
box-shadow: 0 18px 40px rgba(12, 10, 8, 0.22);
color: #2a2622;
}
@@ -328,8 +345,8 @@ onMounted(async () => {
margin-top: 18px;
padding: 0 8px 0 14px;
border: 1px solid #e4e0db;
border-radius: 999px;
background: #f7f5f1;
border-radius: 14px;
background: #e4ddd4;
}
.phone-field.is-invalid {
+2 -1
View File
@@ -359,10 +359,11 @@ onMounted(() => {
flex-direction: column;
min-height: 100%;
width: 100%;
max-width: 360px;
max-width: 390px;
margin-inline: auto;
padding: calc(10px + env(safe-area-inset-top, 0px)) 16px
calc(14px + env(safe-area-inset-bottom, 0px));
box-sizing: border-box;
}
.otp-top {
+35 -30
View File
@@ -5,7 +5,6 @@ import { useRouter } from 'vue-router'
import { useAuthStore } from '@/stores/auth'
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
import FormErrorBanner from '@/components/ui/FormErrorBanner.vue'
import IconBuilding from '@/components/icons/IconBuilding.vue'
import IconChevronLeft from '@/components/icons/IconChevronLeft.vue'
import IconDiamond from '@/components/icons/IconDiamond.vue'
import IconPhone from '@/components/icons/IconPhone.vue'
@@ -216,7 +215,7 @@ async function submit() {
@click="jobOpen = !jobOpen"
>
<span class="reg-field__meta">
<IconBuilding :size="18" color="#8A847C" />
<BasicIcon icon="vuesax-linear:category" :size="18" color="#8A847C" />
<span :class="{ 'is-placeholder': !jobCategory }">{{ selectedJobLabel }}</span>
</span>
<svg class="job-chevron" viewBox="0 0 12 12" aria-hidden="true">
@@ -275,24 +274,24 @@ async function submit() {
</label>
<p v-if="fieldErrors.terms" class="reg-field__error">{{ fieldErrors.terms }}</p>
</form>
<section class="register-perks" aria-label="مزایا">
<article class="register-perk">
<IconVerified :size="22" color="#6F6B66" />
<strong>اطمینان از اصالت محصولات</strong>
</article>
<article class="register-perk">
<BasicIcon icon="vuesax-linear:truck-time" :size="22" color="#6F6B66" />
<strong>دسترسی سریع به کالیته‌های جدید</strong>
</article>
<article class="register-perk">
<IconDiamond :size="22" color="#6F6B66" />
<strong>تجربه خرید ویژه مشتریان</strong>
</article>
</section>
<div class="register-foot">
<img class="register-foot__bg" :src="cardUrl" alt="" />
<section class="register-perks" aria-label="مزایا">
<article class="register-perk">
<IconVerified :size="22" color="#6F6B66" />
<strong>اطمینان از اصالت محصولات</strong>
</article>
<article class="register-perk">
<BasicIcon icon="vuesax-linear:truck-time" :size="22" color="#6F6B66" />
<strong>دسترسی سریع به کالیته‌های جدید</strong>
</article>
<article class="register-perk">
<IconDiamond :size="22" color="#6F6B66" />
<strong>تجربه خرید ویژه مشتریان</strong>
</article>
</section>
<p class="register-foot__copy">همراه حرفه‌ای<br />کسب‌وکار شما</p>
</div>
</div>
@@ -322,6 +321,7 @@ async function submit() {
max-width: 390px;
margin-inline: auto;
padding: calc(10px + env(safe-area-inset-top, 0px)) 20px 0;
box-sizing: border-box;
}
.register-top {
@@ -383,9 +383,9 @@ async function submit() {
height: 52px;
margin-top: 10px;
padding: 0 14px;
border: 1px solid #e6e1db;
border-radius: 999px;
background: #fbfaf7;
border: 1px solid #e0d9d0;
border-radius: 14px;
background: #ebe4dc;
}
.reg-field.is-invalid,
@@ -619,13 +619,15 @@ async function submit() {
.register-perks {
position: relative;
z-index: 2;
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 8px;
margin-bottom: 20rem;
margin: 35px 0 -20px;
padding: 16px 10px 14px;
border-radius: 22px;
background: #f7f3ee;
background: #ebe4dc;
box-shadow: 0 10px 28px rgba(40, 32, 24, 0.12), 0 2px 8px rgba(40, 32, 24, 0.06);
}
.register-perk {
@@ -645,11 +647,14 @@ async function submit() {
.register-foot {
position: relative;
z-index: 1;
display: flex;
flex-direction: column;
margin-top: auto;
margin-inline: -20px;
min-height: 270px;
min-height: 260px;
overflow: hidden;
padding: 20px 20px calc(20px + env(safe-area-inset-bottom, 0px));
padding: 48px 0 calc(16px + env(safe-area-inset-bottom, 0px));
}
.register-foot__bg {
@@ -662,8 +667,8 @@ async function submit() {
object-position: 88% 32%;
pointer-events: none;
user-select: none;
mask-image: linear-gradient(to bottom, transparent 0%, #000 38%);
-webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 38%);
mask-image: linear-gradient(to bottom, transparent 0%, #000 42%);
-webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 42%);
}
.register-foot::before {
@@ -672,16 +677,16 @@ async function submit() {
.register-foot__copy {
position: absolute;
z-index: 3;
z-index: 1;
left: 24px;
top: 8.75rem;
top: 38%;
margin: 0;
max-width: 10rem;
font-size: 0.75rem;
font-weight: 600;
line-height: 1.55;
text-align: right;
color: #5a5550;
color:#1c1b19;
pointer-events: none;
}
+337
View File
@@ -0,0 +1,337 @@
<script setup lang="ts">
import { useRouter } from 'vue-router'
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
import IconChevronLeft from '@/components/icons/IconChevronLeft.vue'
import IconQemashMark from '@/components/icons/IconQemashMark.vue'
import fabricUrl from '@/assets/images/login-fabric.jpg'
const router = useRouter()
/* ترتیب DOM از راست به چپ در RTL: ستاره → کامیون → لایه‌ها */
const features = [
{
icon: 'star',
title: 'همراه همیشگی کسب‌وکارتان',
subtitle: 'با خدمات ویژه مشتریان',
},
{
icon: 'vuesax-linear:truck-time',
title: 'سفارش عمده',
subtitle: 'مستقیم از تولیدکننده',
},
{
icon: 'vuesax-linear:element-3',
title: 'تنوع بی‌نظیر محصولات',
subtitle: 'از کلاسیک تا مدرن',
},
] as const
function goLogin() {
const redirect = typeof router.currentRoute.value.query.redirect === 'string'
? router.currentRoute.value.query.redirect
: undefined
router.push({
name: 'login',
query: redirect ? { redirect } : undefined,
})
}
</script>
<template>
<div class="page welcome-page">
<img class="welcome-page__bg" :src="fabricUrl" alt="" />
<div class="welcome-page__veil" aria-hidden="true" />
<div class="welcome-page__scroll">
<div class="welcome-page__stage">
<header class="welcome-top">
<button type="button" class="welcome-skip" @click="goLogin">رد شدن</button>
</header>
<div class="welcome-brand">
<IconQemashMark :size="88" />
<h1 class="welcome-brand__fa">نادمی تکس</h1>
<p class="welcome-brand__tag">انتخاب حرفه‌ای، شروع یک همکاری ماندگار</p>
<span class="welcome-brand__rule" aria-hidden="true" />
</div>
<ul class="welcome-features">
<li v-for="item in features" :key="item.title" class="welcome-feature">
<span class="welcome-feature__icon" aria-hidden="true">
<svg
v-if="item.icon === 'star'"
class="welcome-feature__star"
viewBox="0 0 24 24"
fill="none"
>
<path
d="M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z"
stroke="#fff"
stroke-width="1.5"
stroke-linejoin="round"
/>
</svg>
<BasicIcon v-else :icon="item.icon" :size="22" color="#ffffff" />
</span>
<strong>{{ item.title }}</strong>
<span>{{ item.subtitle }}</span>
</li>
</ul>
<div class="welcome-spacer" aria-hidden="true" />
<div class="welcome-dots" aria-hidden="true">
<i class="is-on" />
<i />
<i />
<i />
</div>
<button type="button" class="welcome-cta" @click="goLogin">
<span>ورود / ثبت‌نام</span>
<span class="welcome-cta__chevron" aria-hidden="true">
<IconChevronLeft :size="18" color="#ffffff" />
</span>
</button>
</div>
</div>
</div>
</template>
<style scoped>
.welcome-page {
position: relative;
isolation: isolate;
height: 100%;
min-height: 100%;
color: #f4ede4;
background: #1a1612;
overflow: hidden;
}
.welcome-page__bg {
position: absolute;
inset: 0;
z-index: 0;
width: 100%;
height: 100%;
object-fit: cover;
object-position: center;
pointer-events: none;
user-select: none;
}
.welcome-page__veil {
position: absolute;
inset: 0;
z-index: 1;
background:
linear-gradient(180deg, rgba(12, 10, 8, 0.42) 0%, rgba(12, 10, 8, 0.28) 42%, rgba(12, 10, 8, 0.72) 100%);
pointer-events: none;
}
.welcome-page__scroll {
position: relative;
z-index: 2;
height: 100%;
overflow-x: hidden;
overflow-y: auto;
overscroll-behavior: contain;
}
.welcome-page__stage {
display: flex;
flex-direction: column;
min-height: 100%;
width: 100%;
max-width: 390px;
margin-inline: auto;
padding: calc(12px + env(safe-area-inset-top, 0px)) 20px
calc(20px + env(safe-area-inset-bottom, 0px));
box-sizing: border-box;
}
.welcome-top {
display: flex;
justify-content: flex-end;
min-height: 40px;
}
.welcome-skip {
height: 34px;
padding: 0 16px;
border: none;
border-radius: 999px;
background: rgba(12, 10, 8, 0.45);
color: #f4ede4;
font: inherit;
font-size: 0.8125rem;
font-weight: 500;
cursor: pointer;
backdrop-filter: blur(6px);
}
.welcome-brand {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
margin-top: 28px;
}
.welcome-brand__fa {
margin: 14px 0 0;
font-size: 1.875rem;
font-weight: 700;
line-height: 1.3;
color: #ead7c0;
}
.welcome-brand__tag {
margin: 10px 0 0;
max-width: 16rem;
font-size: 0.8125rem;
font-weight: 400;
line-height: 1.7;
color: rgba(244, 237, 228, 0.92);
}
.welcome-brand__rule {
display: block;
width: 36px;
height: 1.5px;
margin-top: 16px;
border-radius: 1px;
background: #c9a56a;
}
.welcome-features {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 10px;
margin: 36px 0 0;
padding: 0;
list-style: none;
}
.welcome-feature {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
text-align: center;
}
.welcome-feature__icon {
display: grid;
place-items: center;
width: 48px;
height: 48px;
border-radius: 999px;
background: rgba(244, 237, 228, 0.22);
color: #fff;
backdrop-filter: blur(4px);
}
.welcome-feature__star {
width: 22px;
height: 22px;
}
.welcome-feature strong {
font-size: 0.6875rem;
font-weight: 700;
line-height: 1.45;
color: #f4ede4;
}
.welcome-feature span {
font-size: 0.5625rem;
font-weight: 400;
line-height: 1.5;
color: rgba(244, 237, 228, 0.72);
}
.welcome-spacer {
flex: 1 1 auto;
min-height: 28px;
}
.welcome-dots {
display: flex;
justify-content: center;
gap: 6px;
margin-bottom: 16px;
}
.welcome-dots i {
width: 6px;
height: 6px;
border-radius: 999px;
background: rgba(244, 237, 228, 0.28);
}
.welcome-dots i.is-on {
background: #f4ede4;
}
.welcome-cta {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 52px;
padding: 0 44px;
border: 1px solid rgba(244, 237, 228, 0.28);
border-radius: 999px;
background:
linear-gradient(135deg, rgba(20, 78, 82, 0.55) 0%, rgba(10, 53, 60, 0.42) 48%, rgba(8, 42, 48, 0.48) 100%);
color: #fff;
font: inherit;
font-size: 0.9375rem;
font-weight: 600;
cursor: pointer;
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.12);
}
.welcome-cta__chevron {
position: absolute;
inset-inline-end: 16px;
display: grid;
place-items: center;
}
@media (min-width: 768px) {
.welcome-page {
padding: 0;
max-width: none;
margin: 0;
height: 100%;
min-height: 0;
overflow: hidden;
}
.welcome-page__stage {
max-width: 390px;
padding-block: 16px 24px;
}
}
@media (max-width: 359px) {
.welcome-page__stage {
padding-inline: 14px;
}
.welcome-brand__fa {
font-size: 1.625rem;
}
.welcome-feature__icon {
width: 42px;
height: 42px;
}
}
</style>