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>
@@ -3,8 +3,8 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="icon" type="image/png" href="/favicon.png?v=2" />
|
||||
<link rel="shortcut icon" href="/favicon.ico?v=2" />
|
||||
<link rel="icon" type="image/png" href="/favicon.png?v=3" />
|
||||
<link rel="shortcut icon" href="/favicon.ico?v=3" />
|
||||
<title>%BRAND_TITLE%</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 37 KiB |
@@ -19,7 +19,7 @@
|
||||
>
|
||||
<div class="nav-header" :class="{ 'is-collapsed': collapsed }">
|
||||
<span class="nav-logo">
|
||||
<img src="../../assets/images/logo/brand-icon.png" alt="" />
|
||||
<img src="../../assets/images/logo/brand-logo.svg" alt="" />
|
||||
</span>
|
||||
<span v-if="!collapsed" class="nav-brand">
|
||||
<span class="nav-title">{{ title }}</span>
|
||||
@@ -207,8 +207,9 @@ function onClick({ key }: { key: string | number }) {
|
||||
padding: 0;
|
||||
}
|
||||
.nav-logo {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
/* wide: the mark is about 1.45:1 */
|
||||
width: 54px;
|
||||
height: 40px;
|
||||
flex-shrink: 0;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
@@ -218,7 +219,7 @@ function onClick({ key }: { key: string | number }) {
|
||||
box-shadow: 0 4px 12px -6px rgba(28, 45, 86, 0.35);
|
||||
}
|
||||
.nav-logo img {
|
||||
width: 64%;
|
||||
width: 86%;
|
||||
}
|
||||
.nav-brand {
|
||||
display: flex;
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<section class="hero" aria-hidden="true">
|
||||
<div class="brand-row">
|
||||
<div class="logo-chip">
|
||||
<img src="../../assets/images/logo/brand-icon.png" alt="" />
|
||||
<img src="../../assets/images/logo/brand-logo.svg" alt="" />
|
||||
</div>
|
||||
<span class="brand-name">{{ brand.nameEn }}</span>
|
||||
</div>
|
||||
@@ -83,7 +83,7 @@
|
||||
<div class="glass-card">
|
||||
<div class="card-head">
|
||||
<div class="logo-chip logo-chip--sm">
|
||||
<img src="../../assets/images/logo/brand-icon.png" :alt="brand.nameEn" />
|
||||
<img src="../../assets/images/logo/brand-logo.svg" :alt="brand.nameEn" />
|
||||
</div>
|
||||
<span class="eyebrow"><span class="dot"></span>پنل مدیریت</span>
|
||||
</div>
|
||||
@@ -347,7 +347,8 @@ async function loginJWT() {
|
||||
gap: 12px;
|
||||
}
|
||||
.logo-chip {
|
||||
width: 52px;
|
||||
/* wide: the mark is about 1.45:1 */
|
||||
width: 72px;
|
||||
height: 52px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
@@ -358,7 +359,7 @@ async function loginJWT() {
|
||||
0 10px 30px -8px rgba(242, 201, 92, 0.45);
|
||||
}
|
||||
.logo-chip img {
|
||||
width: 64%;
|
||||
width: 82%;
|
||||
}
|
||||
.brand-name {
|
||||
font-size: 16px;
|
||||
@@ -492,7 +493,7 @@ async function loginJWT() {
|
||||
justify-content: space-between;
|
||||
}
|
||||
.logo-chip--sm {
|
||||
width: 44px;
|
||||
width: 60px;
|
||||
height: 44px;
|
||||
border-radius: 14px;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<div class="flex gap-6 w-full justify-center h-screen branch-login-page">
|
||||
<div class="flex flex-col h-screen items-center justify-center">
|
||||
<img
|
||||
src="../../assets/images/logo/brand-icon.png"
|
||||
src="../../assets/images/logo/brand-logo.svg"
|
||||
width="150"
|
||||
alt="login"
|
||||
class="mx-auto mb-5"
|
||||
|
||||
@@ -121,7 +121,7 @@
|
||||
|
||||
<ul>
|
||||
<li class="comment-item">
|
||||
<img src="../../../assets/images/logo/brand-icon.png" alt="comment author" class="img border" />
|
||||
<img src="../../../assets/images/logo/brand-logo.svg" alt="comment author" class="img border" />
|
||||
<div class="comment-body">
|
||||
<h5>ادمین</h5>
|
||||
<span v-if="tempRow.reply && tempRow.reply[0]" class="time">
|
||||
@@ -271,6 +271,12 @@ async function submitChanges(row: Record<string, any>) {
|
||||
border: 1px solid grey;
|
||||
border-radius: 50%;
|
||||
}
|
||||
img.img {
|
||||
object-fit: contain;
|
||||
padding: 10px;
|
||||
box-sizing: border-box;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
@media (max-width: 575px) {
|
||||
.comment-item {
|
||||
|
||||
@@ -142,6 +142,12 @@
|
||||
un-checked-children="غیرفعال"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="نمایش به">
|
||||
<a-radio-group v-model:value="customButton.audience">
|
||||
<a-radio value="all">همه (قبل و بعد از ورود)</a-radio>
|
||||
<a-radio value="loggedIn">فقط بعد از ورود</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item label="عنوان دکمه">
|
||||
<a-input
|
||||
v-model:value="customButton.title"
|
||||
@@ -248,7 +254,12 @@ const tagsInputValue = ref('');
|
||||
const sliders = ref<{ image: string; link: string }[]>([]);
|
||||
|
||||
// ── custom button (Setting key: custom_button)
|
||||
const customButton = ref({ enabled: false, title: '', link: '' });
|
||||
const customButton = ref<{ enabled: boolean; title: string; link: string; audience: 'all' | 'loggedIn' }>({
|
||||
enabled: false,
|
||||
title: '',
|
||||
link: '',
|
||||
audience: 'all',
|
||||
});
|
||||
|
||||
// ── collections
|
||||
const collections = ref<{ id: string; title: string }[]>([]);
|
||||
@@ -360,6 +371,7 @@ async function saveCustomButton() {
|
||||
enabled: customButton.value.enabled,
|
||||
title,
|
||||
link,
|
||||
audience: customButton.value.audience,
|
||||
},
|
||||
});
|
||||
message.success('دکمه سفارشی ذخیره شد');
|
||||
@@ -428,6 +440,7 @@ onMounted(async () => {
|
||||
enabled: Boolean(btn.data.enabled),
|
||||
title: String(btn.data.title ?? ''),
|
||||
link: String(btn.data.link ?? ''),
|
||||
audience: btn.data.audience === 'loggedIn' ? 'loggedIn' : 'all',
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
@@ -24,18 +24,23 @@ export class PublicSliderController {
|
||||
return Array.isArray(value) ? value : [];
|
||||
}
|
||||
|
||||
/** Custom button on the profile dashboard (ثابتها → دکمه سفارشی). */
|
||||
/**
|
||||
* Custom button on the profile dashboard (ثابتها → دکمه سفارشی).
|
||||
* `audience`: 'all' = signed out too, 'loggedIn' = only after login. Rows
|
||||
* saved before the field existed were shown to everyone, so they read 'all'.
|
||||
*/
|
||||
@Get('custom-button')
|
||||
async getCustomButton() {
|
||||
const value = await this.misc.getConst('custom_button');
|
||||
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
||||
return { enabled: false, title: '', link: '' };
|
||||
return { enabled: false, title: '', link: '', audience: 'all' };
|
||||
}
|
||||
const row = value as Record<string, unknown>;
|
||||
return {
|
||||
enabled: Boolean(row.enabled),
|
||||
title: String(row.title ?? ''),
|
||||
link: String(row.link ?? ''),
|
||||
audience: row.audience === 'loggedIn' ? 'loggedIn' : 'all',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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" />
|
||||
|
||||
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 28 KiB |
@@ -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
|
||||
|
||||
|
Before Width: | Height: | Size: 82 KiB |
|
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,
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 ? 'شماره موبایل خود را وارد کنید' : 'رمز یکبارمصرف را وارد کنید' }}
|
||||
|
||||