Files
CRM_Montzar/apps/web/src/components/dashboard/HomeHero.vue
T
Mahdi 4979aa8b75 feat(web): refresh Montact brand and sidebar chrome
Ship the new logo assets, set primary to #10897d, and polish sidebar/home-hero UX around roles, org switcher, and the ask bar.
2026-11-05 17:10:03 +03:30

394 lines
8.7 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { computed } from 'vue';
import { SendOutlined } from '@/icons/ant-bridge';
import {
Button as AButton,
Input as AInput,
Typography,
} from 'ant-design-vue';
import { brand } from '@mento/shared';
import { useAuthStore } from '@/stores/auth';
const props = defineProps<{
greeting: string;
firstName: string;
userInitial: string;
askQuery: string;
asking?: boolean;
askAnswer?: string;
askError?: string;
statusText?: string;
}>();
const emit = defineEmits<{
'update:askQuery': [value: string];
ask: [];
}>();
const auth = useAuthStore();
const { Title: ATypographyTitle, Text: ATypographyText, Paragraph: ATypographyParagraph } =
Typography;
/** فقط عکس واقعی کاربر؛ پیش‌فرض عمومی را نشان نده */
const hasCustomPhoto = computed(() => Boolean(auth.user?.hasAvatar));
const shortcutLabel = computed(() =>
typeof navigator !== 'undefined' && /Mac|iPhone|iPad/i.test(navigator.platform)
? '⌘K'
: 'Ctrl+K',
);
const statusMessage = computed(() => {
if (props.asking) return props.statusText || 'در حال پاسخ…';
if (props.askError) return props.askError;
if (props.askAnswer) return props.askAnswer;
return '';
});
</script>
<template>
<div class="flex flex-col gap-3">
<div class="home-hero">
<div class="home-hero__gradient" aria-hidden="true" />
<div class="home-hero__pattern" aria-hidden="true" />
<div class="home-hero__orb home-hero__orb--start" aria-hidden="true" />
<div class="home-hero__orb home-hero__orb--end" aria-hidden="true" />
<div class="home-hero__row">
<div class="home-hero__greet">
<div class="home-hero__identity" :aria-label="firstName">
<img
v-if="hasCustomPhoto"
:src="auth.avatarSrc"
:alt="firstName"
class="home-hero__photo"
/>
<span v-else class="home-hero__monogram" aria-hidden="true">
{{ userInitial }}
</span>
<span class="home-hero__online" title="آنلاین" aria-hidden="true" />
</div>
<div class="home-hero__copy">
<p class="home-hero__brand">{{ brand.nameFa }}</p>
<a-typography-title :level="4" class="home-hero__title">
{{ greeting }}، {{ firstName }}
</a-typography-title>
<a-typography-text class="home-hero__subtitle">
از داده‌ها بپرسید یا کار امروز را از همین‌جا شروع کنید
</a-typography-text>
</div>
</div>
<div class="home-hero__ask">
<a-input
id="home-hero-ask"
:value="askQuery"
:placeholder="brand.askPlaceholderFa"
:disabled="asking"
:bordered="false"
class="home-hero__ask-input"
@update:value="emit('update:askQuery', $event)"
@press-enter="emit('ask')"
/>
<kbd class="home-hero__ask-kbd">{{ shortcutLabel }}</kbd>
<a-button
type="primary"
shape="round"
class="home-hero__ask-btn"
:disabled="asking"
:loading="asking"
aria-label="ارسال"
@click="emit('ask')"
>
<span class="home-hero__ask-btn-inner">
<SendOutlined />
<span class="home-hero__ask-label">بپرس</span>
</span>
</a-button>
</div>
</div>
</div>
<div
v-if="statusMessage"
class="rounded-xl border px-3.5 py-2.5"
:class="
askError
? 'border-rose-200 bg-rose-50/70'
: asking
? 'border-emerald-200/70 bg-emerald-50/50'
: 'border-slate-200 bg-slate-50/80'
"
>
<a-typography-paragraph
:type="askError ? 'danger' : 'secondary'"
:ellipsis="{ rows: 3 }"
class="!mb-0 !text-sm"
>
{{ statusMessage }}
</a-typography-paragraph>
</div>
</div>
</template>
<style scoped>
.home-hero {
position: relative;
display: block;
width: 100%;
max-width: 100%;
min-width: 0;
box-sizing: border-box;
overflow: hidden;
border-radius: 1rem;
border: 1px solid rgb(16 137 125 / 0.28);
box-shadow: 0 1px 2px rgb(15 23 42 / 0.04);
}
.home-hero__gradient,
.home-hero__pattern,
.home-hero__orb {
pointer-events: none;
position: absolute;
}
.home-hero__gradient,
.home-hero__pattern {
inset: 0;
}
.home-hero__gradient {
background:
linear-gradient(
115deg,
#eef8f7 0%,
#d0ebe8 28%,
#c5e6e2 55%,
#eef8f7 78%,
#ffffff 100%
);
}
.home-hero__pattern {
opacity: 0.45;
background-image:
radial-gradient(circle at 1px 1px, rgba(16, 137, 125, 0.28) 1px, transparent 0),
linear-gradient(
135deg,
rgba(16, 137, 125, 0.08) 25%,
transparent 25%,
transparent 50%,
rgba(16, 137, 125, 0.08) 50%,
rgba(16, 137, 125, 0.08) 75%,
transparent 75%,
transparent
);
background-size:
18px 18px,
28px 28px;
mask-image: linear-gradient(to left, rgba(0, 0, 0, 0.85), rgba(0, 0, 0, 0.25));
}
.home-hero__orb {
border-radius: 999px;
filter: blur(48px);
}
.home-hero__orb--start {
inset-inline-end: -5rem;
top: -6rem;
width: 13rem;
height: 13rem;
background: rgb(22 193 168 / 0.22);
}
.home-hero__orb--end {
inset-inline-start: -4rem;
bottom: -5rem;
width: 12rem;
height: 12rem;
background: rgb(16 137 125 / 0.16);
}
.home-hero__row {
position: relative;
z-index: 1;
display: grid;
grid-template-columns: auto minmax(0, 1fr);
align-items: center;
gap: 1rem;
width: 100%;
max-width: 100%;
min-width: 0;
box-sizing: border-box;
padding: 1rem 1.25rem;
}
.home-hero__greet {
display: flex;
align-items: center;
gap: 0.875rem;
min-width: 0;
max-width: 22rem;
}
.home-hero__identity {
position: relative;
flex-shrink: 0;
width: 3rem;
height: 3rem;
}
.home-hero__photo,
.home-hero__monogram {
display: flex;
width: 100%;
height: 100%;
border-radius: 0.85rem;
object-fit: cover;
}
.home-hero__monogram {
align-items: center;
justify-content: center;
font-size: 1.1rem;
font-weight: 800;
color: #eef8f7;
background: linear-gradient(160deg, #16c1a8 0%, #10897d 100%);
letter-spacing: -0.02em;
user-select: none;
}
.home-hero__online {
position: absolute;
inset-inline-start: 0;
bottom: 0;
width: 0.7rem;
height: 0.7rem;
border-radius: 999px;
background: #10897d;
border: 2px solid #fff;
}
.home-hero__copy {
min-width: 0;
}
.home-hero__brand {
margin: 0;
font-size: 11px;
font-weight: 600;
letter-spacing: 0.02em;
color: rgb(4 120 87 / 0.8);
}
.home-hero__title {
margin: 0.125rem 0 0 !important;
color: #1e293b !important;
font-size: 1.125rem !important;
font-weight: 800 !important;
line-height: 1.75rem !important;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.home-hero__subtitle {
display: block !important;
margin-top: 0.125rem !important;
color: #64748b !important;
font-size: 0.875rem !important;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.home-hero__ask {
display: flex;
align-items: center;
gap: 0.5rem;
width: 100%;
min-width: 0;
box-sizing: border-box;
padding-block: 0.375rem;
padding-inline: 0.75rem 0.5rem;
background: #fff;
border: 1px solid rgb(255 255 255 / 0.8);
border-radius: 1rem;
box-shadow:
0 4px 12px rgb(6 78 59 / 0.05),
0 1px 2px rgb(15 23 42 / 0.04);
transition:
border-color 0.15s ease,
box-shadow 0.15s ease;
}
.home-hero__ask:focus-within {
border-color: #16c1a8;
box-shadow:
0 0 0 2px rgb(16 137 125 / 0.18),
0 4px 12px rgb(10 92 84 / 0.06);
}
.home-hero__ask-input {
flex: 1 1 auto;
min-width: 0;
background: transparent !important;
}
.home-hero__ask-kbd {
display: none;
flex-shrink: 0;
align-items: center;
padding: 0.25rem 0.5rem;
border-radius: 0.5rem;
border: 1px solid #e2e8f0;
background: #f8fafc;
color: #94a3b8;
font-size: 11px;
font-family: ui-sans-serif, system-ui, sans-serif;
}
.home-hero__ask-btn {
display: inline-flex !important;
align-items: center;
justify-content: center;
}
.home-hero__ask-btn-inner {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.35rem;
line-height: 1;
}
.home-hero__ask-label {
display: none;
}
@media (min-width: 640px) {
.home-hero__ask-kbd {
display: inline-flex;
}
.home-hero__ask-label {
display: inline;
}
.home-hero__title {
font-size: 1.25rem !important;
}
}
@media (max-width: 1023px) {
.home-hero__row {
grid-template-columns: minmax(0, 1fr);
}
.home-hero__greet {
max-width: none;
}
}
</style>