feat: new Tehrani brand logo, custom button audience, trim dashboard

- Replace the crown logo with logo.svg / logo_bk.svg across both frontends;
  regenerate favicons and PWA icons via scripts/generate-brand-icons.cjs
- Custom button: admin chooses whether it shows before login or only after
- Dashboard: drop the survey-majority banner, hide the custom-order row

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
mahmood
2026-11-14 16:53:25 +03:30
co-authored by Cursor
parent 0f6ad79ae7
commit de13445109
37 changed files with 428 additions and 105 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
<!doctype html>
<html lang="en">
<head>
<link rel="icon" href="/favicon.ico" />
<link rel="icon" href="/favicon.ico?v=3" />
<meta charset="UTF-8" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 14 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 43 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 28 KiB

+2
View File
@@ -332,6 +332,8 @@ export interface CustomButton {
enabled: boolean;
title: string;
link: string;
/** 'all' = also before login; 'loggedIn' = only signed-in customers. Absent on older APIs = 'all'. */
audience?: 'all' | 'loggedIn';
}
/** One admin-uploaded home banner slide — raw shape as saved by the admin
Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 36 KiB

@@ -732,6 +732,11 @@ html.theme-dark body {
color: @ink;
}
// The brand mark is black line art; on the dark canvas it is drawn light.
html.theme-dark .brand-mark {
filter: invert(1);
}
html.theme-dark {
.ant-input,
.ant-input-affix-wrapper,
+1 -1
View File
@@ -2,7 +2,7 @@
<template>
<Spin :spinning="spinning" :tip="tip" :size="(size as 'small' | 'default' | 'large')" :delay="delay" :class="fixed ? '!max-h-screen !fixed' : ''">
<template #indicator>
<img v-if="!indicator" src="/img/brand-logo.png" :alt="brand.nameEn" class="spinner-brand" width="48" height="48" />
<img v-if="!indicator" src="/img/brand-logo.svg" :alt="brand.nameEn" class="spinner-brand brand-mark" width="48" height="48" />
<!-- keep-motion: a working spinner, exempt from reduced motion. -->
<div class="loading keep-motion" v-else>
<div class="effect-1 effects"></div>
@@ -22,10 +22,10 @@
>
<img
class="rfx__logo"
src="/img/brand-logo.png"
src="/img/brand-logo-card.svg"
:alt="brand.name"
width="140"
height="140"
width="168"
height="117"
/>
<div class="rfx__spin" aria-hidden="true">
<Loading type="spinner" size="28px" color="#fff" />
@@ -88,8 +88,8 @@ const blocking = computed(() => props.intensity >= 0.99)
}
.rfx__logo {
width: 140px;
height: 140px;
width: 168px;
height: 117px;
object-fit: contain;
filter: drop-shadow(0 22px 44px rgba(0, 0, 0, 0.5))
drop-shadow(0 0 36px rgba(124, 0, 53, 0.4));
+1 -1
View File
@@ -25,7 +25,7 @@ import { defineStore } from 'pinia'
import { api } from '../api/client'
import shippingArt from '../assets/img/shipping-and-delivery.png'
import competitionArt from '../assets/img/sports-and-competition.png'
import brandArt from '../assets/img/brand-logo.png'
import brandArt from '../assets/img/brand-logo.svg'
import { brand } from '../brand'
export interface Slide {
+1 -1
View File
@@ -22,7 +22,7 @@
<div class="home">
<header class="home__bar">
<div class="home__barInner">
<img src="/img/brand-logo.png" :alt="brand.name" class="home__logo" />
<img src="/img/brand-logo.svg" :alt="brand.name" class="home__logo brand-mark" />
<RateStrip :quote="goldPrice" compact class="home__rate" />
</div>
</header>
@@ -116,7 +116,7 @@
<!-- Signed out: same card shell as the logged-in hero; logo + CTA only. -->
<section v-else class="dash__hero dash__invite dash__reveal" :style="revealDelay(0)">
<img class="dash__inviteLogo" src="/img/brand-logo.png" :alt="brand.name" />
<img class="dash__inviteLogo" src="/img/brand-logo.svg" :alt="brand.name" />
<button type="button" class="dash__inviteCta" @click="router.push({ name: 'login' })">
ورود / ثبت‌نام
</button>
@@ -141,25 +141,6 @@
</span>
</button>
<button
v-if="session.isLoggedIn && surveyMajorityHits.length"
type="button"
class="dash__banner dash__banner--survey dash__reveal"
:style="revealDelay(1)"
@click="goToDetails(8)"
>
<span class="dash__bannerIcon" aria-hidden="true">
<Icon icon="vuesax-linear:like-1" size="18" />
</span>
<span class="dash__bannerText">
<b>{{ surveyMajorityHits.length }} طرح به حد اکثریت رسید</b>
<small>{{ surveyMajorityHits[0]?.title || 'بدون عنوان' }}{{ surveyMajorityHits.length > 1 ? ' و …' : '' }}</small>
</span>
<span class="dash__bannerChevron" aria-hidden="true">
<Icon icon="vuesax-linear:arrow-left-1" size="16" />
</span>
</button>
<!-- Settings-style rows: every destination the hero no longer carries
as its own quick-action row. -->
<section class="dash__rows dash__reveal" :style="revealDelay(1)">
@@ -216,6 +197,7 @@
mcode, so every signed-in customer sees it and the server scopes it
to their token.
-->
<!-- «سفارش ساخت» hidden for now.
<button type="button" class="dash__row" @click="goToDetails(7)">
<span class="dash__rowIcon" aria-hidden="true">
<Icon icon="vuesax-linear:3d-cube-scan" size="18" />
@@ -227,6 +209,7 @@
<Icon icon="vuesax-linear:arrow-left-1" size="16" />
</span>
</button>
-->
<button type="button" class="dash__row" @click="goToDetails(8)">
<span class="dash__rowIcon" aria-hidden="true">
@@ -354,7 +337,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 CustomButton, type CustomerDetail, type GoldPriceQuote, type ProductSurveyRow } from '../../api/client'
import { api, type CustomButton, type CustomerDetail, type GoldPriceQuote } from '../../api/client'
import { useCachedReads } from '../../api/cachedReads'
import { KEYS } from '../../stores/queryCache'
@@ -365,12 +348,12 @@ 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 surveyMajorityHits = ref<ProductSurveyRow[]>([])
const refreshing = ref(false)
const profileError = ref(false)
const showCustomButton = computed(() => {
const btn = customButton.value
if (btn?.audience === 'loggedIn' && !session.isLoggedIn) return false
return Boolean(btn?.enabled && btn.title.trim() && btn.link.trim())
})
@@ -481,17 +464,6 @@ async function loadDashboard() {
}
goldPrice.value = price.status === 'fulfilled' ? price.value : null
if (btn.status === 'fulfilled') customButton.value = btn.value
if (session.isLoggedIn) {
try {
const surveys = await api.listOpenProductSurveys()
surveyMajorityHits.value = surveys.majorityHits ?? surveys.rows.filter((r) => r.reachedMajority)
} catch {
surveyMajorityHits.value = []
}
} else {
surveyMajorityHits.value = []
}
}
async function onRefresh() {
@@ -984,11 +956,6 @@ onMounted(async () => {
overflow: hidden;
text-overflow: ellipsis;
}
.dash__banner--survey {
background: linear-gradient(115deg, @green 0%, #1f7a4d 100%);
box-shadow: 0 10px 24px -16px alpha(@green, 70%);
}
.dash__bannerChevron {
flex: 0 0 auto;
width: 28px;
@@ -23,7 +23,7 @@
<container class="auth__body">
<section class="auth__intro">
<img class="auth__logo" src="/img/brand-logo.png" :alt="brand.name" />
<img class="auth__logo brand-mark" src="/img/brand-logo.svg" :alt="brand.name" />
<h2 class="auth__title">ورود به {{ brand.name }}</h2>
<p class="auth__sub">
{{ body.step === 1 ? 'شماره موبایل خود را وارد کنید' : 'رمز یکبارمصرف را وارد کنید' }}