Unify inner page back headers

This commit is contained in:
Ruslan Bakiev
2026-04-06 21:44:24 +07:00
parent fe775cc968
commit a820d1f7ee
13 changed files with 140 additions and 136 deletions

View File

@@ -0,0 +1,38 @@
<script setup lang="ts">
defineProps<{
to: string;
backLabel: string;
title: string;
subtitle?: string;
}>();
</script>
<template>
<div class="flex flex-col gap-4 md:flex-row md:items-start md:justify-between">
<div class="min-w-0 space-y-3">
<div class="flex flex-wrap items-center gap-3 px-1">
<NuxtLink
:to="to"
:aria-label="backLabel"
class="flex h-11 w-11 shrink-0 items-center justify-center rounded-full bg-white/70 text-[#0d854a] transition hover:bg-white"
>
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="none">
<path d="M11.5 4.5L6 10L11.5 15.5" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</NuxtLink>
<h1 class="min-w-0 text-2xl font-black tracking-[-0.03em] text-[#123824] md:text-3xl">
{{ title }}
</h1>
</div>
<p v-if="subtitle" class="max-w-3xl pl-[3.5rem] text-sm leading-6 text-[#466653]">
{{ subtitle }}
</p>
</div>
<div v-if="$slots.actions" class="flex shrink-0 flex-wrap gap-2 md:justify-end">
<slot name="actions" />
</div>
</div>
</template>

View File

@@ -39,8 +39,6 @@ function formatDateTime(value: string) {
<template> <template>
<section class="space-y-6"> <section class="space-y-6">
<NuxtLink to="/admin/bonuses/balances" class="text-sm font-semibold text-[#0d854a]"> Назад к бонусам</NuxtLink>
<div v-if="bonusAccountQuery.loading.value" class="manager-empty-state"> <div v-if="bonusAccountQuery.loading.value" class="manager-empty-state">
Загружаем бонусный счёт... Загружаем бонусный счёт...
</div> </div>
@@ -51,18 +49,19 @@ function formatDateTime(value: string) {
<template v-else> <template v-else>
<div class="space-y-4"> <div class="space-y-4">
<div class="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between"> <UiBackHeader
<div class="space-y-1"> to="/admin/bonuses/balances"
<h1 class="text-3xl font-extrabold text-[#123824]">{{ bonusAccount.fullName }}</h1> back-label="Назад к бонусам"
<p v-if="bonusAccount.companyName || bonusAccount.email" class="text-sm text-[#5c7b69]"> :title="`Бонусный счёт ${bonusAccount.fullName}`"
{{ bonusAccount.companyName || bonusAccount.email }} :subtitle="bonusAccount.companyName || bonusAccount.email || undefined"
</p> >
</div> <template #actions>
<div class="text-left sm:text-right"> <div class="text-left md:text-right">
<p class="text-[11px] font-semibold uppercase tracking-[0.18em] text-[#6a8a76]">Доступный бонус</p> <p class="text-[11px] font-semibold uppercase tracking-[0.18em] text-[#6a8a76]">Доступный бонус</p>
<p class="mt-2 text-3xl font-black leading-none text-[#123824]">{{ formatAmount(bonusAccount.balance) }}</p> <p class="mt-2 text-3xl font-black leading-none text-[#123824]">{{ formatAmount(bonusAccount.balance) }}</p>
</div> </div>
</div> </template>
</UiBackHeader>
<div v-if="pendingWithdrawals.length" class="surface-card rounded-[32px] p-6"> <div v-if="pendingWithdrawals.length" class="surface-card rounded-[32px] p-6">
<p class="text-lg font-bold text-[#123824]">Выводы</p> <p class="text-lg font-bold text-[#123824]">Выводы</p>

View File

@@ -103,15 +103,12 @@ async function createReferral() {
<template> <template>
<section class="space-y-6 max-w-3xl"> <section class="space-y-6 max-w-3xl">
<NuxtLink to="/admin/bonuses/balances" class="text-sm font-semibold text-[#0d854a]"> Назад к бонусам</NuxtLink> <UiBackHeader
to="/admin/bonuses/balances"
<div class="manager-hero"> back-label="Назад к бонусам"
<p class="manager-eyebrow">Бонусы</p> title="Создать бонусную связку клиентов"
<h1 class="manager-title">Создать бонусную связку клиентов</h1> subtitle="Первый клиент получает процент бонуса, когда заказ второго клиента переходит в статус доставленного."
<p class="max-w-2xl text-sm text-[#466653]"> />
Первый клиент получает процент бонуса, когда заказ второго клиента переходит в статус доставленного.
</p>
</div>
<div class="surface-card rounded-3xl p-5 space-y-4"> <div class="surface-card rounded-3xl p-5 space-y-4">
<label class="form-control"> <label class="form-control">

View File

@@ -31,12 +31,11 @@ async function addBonus() {
<template> <template>
<section class="space-y-6 max-w-3xl"> <section class="space-y-6 max-w-3xl">
<NuxtLink to="/admin/bonuses/balances" class="text-sm font-semibold text-[#0d854a]"> Назад к бонусам</NuxtLink> <UiBackHeader
to="/admin/bonuses/balances"
<div class="manager-hero"> back-label="Назад к бонусам"
<p class="manager-eyebrow">Бонусы</p> title="Добавить бонусную транзакцию"
<h1 class="manager-title">Добавить бонусную транзакцию</h1> />
</div>
<div class="surface-card rounded-3xl p-5 space-y-3"> <div class="surface-card rounded-3xl p-5 space-y-3">
<label class="form-control"> <label class="form-control">

View File

@@ -49,8 +49,6 @@ async function reviewWithdrawal() {
<template> <template>
<section class="space-y-6 max-w-3xl"> <section class="space-y-6 max-w-3xl">
<NuxtLink to="/admin/bonuses/requests" class="text-sm font-semibold text-[#0d854a]"> Назад к бонусам</NuxtLink>
<div v-if="withdrawalsQuery.loading.value" class="manager-empty-state"> <div v-if="withdrawalsQuery.loading.value" class="manager-empty-state">
Загружаем заявку на вывод... Загружаем заявку на вывод...
</div> </div>
@@ -60,13 +58,12 @@ async function reviewWithdrawal() {
</div> </div>
<template v-else> <template v-else>
<div class="manager-hero"> <UiBackHeader
<p class="manager-eyebrow">Вывод</p> to="/admin/bonuses/requests"
<h1 class="manager-title">Проверка заявки на вывод</h1> back-label="Назад к бонусам"
<p class="manager-copy"> title="Проверка заявки на вывод"
{{ currentWithdrawal.requesterFullName }} · {{ currentWithdrawal.requesterEmail }} · Сумма: {{ currentWithdrawal.amount }} :subtitle="`${currentWithdrawal.requesterFullName} · ${currentWithdrawal.requesterEmail} · Сумма: ${currentWithdrawal.amount}`"
</p> />
</div>
<div class="surface-card rounded-3xl p-5 space-y-3"> <div class="surface-card rounded-3xl p-5 space-y-3">
<label class="form-control"> <label class="form-control">

View File

@@ -316,20 +316,11 @@ watch(
</div> </div>
<template v-else> <template v-else>
<div class="surface-card rounded-3xl px-5 py-4"> <UiBackHeader
<div class="flex flex-wrap items-center gap-3"> to="/admin/orders"
<NuxtLink to="/admin/orders" class="text-sm font-semibold text-[#0d854a]"> back-label="Назад к заказам клиентов"
Назад к заказам клиентов :title="`Заказ ${currentOrderCode}`"
</NuxtLink> />
<span class="hidden h-4 w-px bg-[#d8e4dd] md:block" />
<span class="text-[11px] font-semibold uppercase tracking-[0.18em] text-[#6a8a76]">
Заказ
</span>
<span class="text-lg font-black tracking-[-0.03em] text-[#123824]">
{{ currentOrderCode }}
</span>
</div>
</div>
<div class="space-y-4"> <div class="space-y-4">
<div class="surface-card rounded-3xl p-5"> <div class="surface-card rounded-3xl p-5">

View File

@@ -107,8 +107,6 @@ async function rejectRequest() {
<template> <template>
<section class="space-y-6"> <section class="space-y-6">
<NuxtLink :to="backTarget" class="text-sm font-semibold text-[#0d854a]"> Назад к пользователям</NuxtLink>
<template v-if="isRequestMode"> <template v-if="isRequestMode">
<div v-if="requestsQuery.loading.value" class="manager-empty-state"> <div v-if="requestsQuery.loading.value" class="manager-empty-state">
Загружаем карточку клиента... Загружаем карточку клиента...
@@ -119,18 +117,19 @@ async function rejectRequest() {
</div> </div>
<template v-else> <template v-else>
<div class="flex flex-col gap-3 md:flex-row md:items-start md:justify-between"> <UiBackHeader
<div class="manager-hero"> :to="backTarget"
<p class="manager-eyebrow">Заявка</p> back-label="Назад к пользователям"
<h1 class="manager-title">{{ currentRequest.companyName }}</h1> :title="`Заявка ${currentRequest.companyName}`"
<p class="manager-copy">Контакт: {{ currentRequest.contactName }} · {{ currentRequest.email }}</p> :subtitle="`Контакт: ${currentRequest.contactName} · ${currentRequest.email}`"
</div> >
<template #actions>
<div v-if="currentRequest.status === 'PENDING'" class="flex flex-wrap gap-2"> <div v-if="currentRequest.status === 'PENDING'" class="flex flex-wrap gap-2">
<button class="btn btn-success border-0" @click="approveRequest">Одобрить</button> <button class="btn btn-success border-0" @click="approveRequest">Одобрить</button>
<button class="btn btn-error border-0" @click="rejectRequest">Отклонить</button> <button class="btn btn-error border-0" @click="rejectRequest">Отклонить</button>
</div> </div>
</div> </template>
</UiBackHeader>
<div class="grid gap-4 lg:grid-cols-3"> <div class="grid gap-4 lg:grid-cols-3">
<div class="manager-stat-card"> <div class="manager-stat-card">
@@ -161,6 +160,13 @@ async function rejectRequest() {
</div> </div>
<template v-else> <template v-else>
<UiBackHeader
:to="backTarget"
back-label="Назад к пользователям"
:title="`Клиент ${currentUser.fullName}`"
:subtitle="currentUser.companyName || currentUser.email"
/>
<div class="rounded-[36px] bg-[#edf3ee] p-6 md:p-8"> <div class="rounded-[36px] bg-[#edf3ee] p-6 md:p-8">
<div class="flex flex-col gap-6 md:flex-row md:items-start"> <div class="flex flex-col gap-6 md:flex-row md:items-start">
<div class="flex shrink-0 justify-center md:block"> <div class="flex shrink-0 justify-center md:block">
@@ -179,11 +185,6 @@ async function rejectRequest() {
</div> </div>
<div class="min-w-0 flex-1 space-y-5"> <div class="min-w-0 flex-1 space-y-5">
<div class="space-y-2">
<p class="text-[11px] font-semibold uppercase tracking-[0.18em] text-[#6a8a76]">Клиент</p>
<h1 class="text-3xl font-black tracking-[-0.03em] text-[#123824]">{{ currentUser.fullName }}</h1>
</div>
<div class="grid gap-3 md:grid-cols-2 xl:grid-cols-4"> <div class="grid gap-3 md:grid-cols-2 xl:grid-cols-4">
<div class="rounded-[24px] bg-white/70 px-4 py-3"> <div class="rounded-[24px] bg-white/70 px-4 py-3">
<p class="text-[11px] font-semibold uppercase tracking-[0.18em] text-[#6a8a76]">Email</p> <p class="text-[11px] font-semibold uppercase tracking-[0.18em] text-[#6a8a76]">Email</p>

View File

@@ -39,13 +39,12 @@ async function createInvitation() {
<template> <template>
<section class="space-y-6 max-w-3xl"> <section class="space-y-6 max-w-3xl">
<NuxtLink to="/admin/orders/clients" class="text-sm font-semibold text-[#0d854a]"> Назад к пользователям</NuxtLink> <UiBackHeader
to="/admin/orders/clients"
<div class="manager-hero"> back-label="Назад к пользователям"
<p class="manager-eyebrow">Приглашение</p> title="Пригласить нового клиента"
<h1 class="manager-title">Пригласить нового клиента</h1> subtitle="Форма вынесена отдельно, чтобы список клиентов оставался чистым и спокойным."
<p class="manager-copy">Форма вынесена отдельно, чтобы список клиентов оставался чистым и спокойным.</p> />
</div>
<div class="surface-card rounded-3xl p-5"> <div class="surface-card rounded-3xl p-5">
<div class="grid gap-3"> <div class="grid gap-3">

View File

@@ -32,20 +32,11 @@ const currentOrderCode = computed(() => formatOrderCode(currentOrder.value?.code
</div> </div>
<template v-else> <template v-else>
<div class="flex flex-wrap items-center gap-3 px-1"> <UiBackHeader
<NuxtLink
to="/orders" to="/orders"
aria-label="Назад к моим заказам" back-label="Назад к моим заказам"
class="flex h-11 w-11 items-center justify-center rounded-full bg-white/70 text-[#0d854a] transition hover:bg-white" :title="`Заказ ${currentOrderCode}`"
> />
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="none">
<path d="M11.5 4.5L6 10L11.5 15.5" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</NuxtLink>
<h1 class="text-2xl font-black tracking-[-0.03em] text-[#123824] md:text-3xl">
Заказ {{ currentOrderCode }}
</h1>
</div>
<div class="space-y-4"> <div class="space-y-4">
<OrdersOrderStatusTimelineCard <OrdersOrderStatusTimelineCard

View File

@@ -61,23 +61,21 @@ async function deleteAddress(addressId: string) {
<template> <template>
<section class="space-y-6"> <section class="space-y-6">
<NuxtLink to="/profile" class="link link-hover text-sm"> Назад в профиль</NuxtLink> <UiBackHeader
to="/profile"
<div class="flex flex-col gap-4 md:flex-row md:items-end md:justify-between"> back-label="Назад в профиль"
<div class="space-y-2"> title="Адреса доставки"
<h1 class="text-3xl font-extrabold text-[#0f2f20]">Адреса доставки</h1> subtitle="Выберите основной адрес для заказов или добавьте новый."
<p class="text-sm leading-6 text-[#466653]"> >
Выберите основной адрес для заказов или добавьте новый. <template #actions>
</p>
</div>
<NuxtLink <NuxtLink
to="/profile/addresses/new" to="/profile/addresses/new"
class="btn rounded-full border-0 bg-[#123824] px-6 text-white hover:bg-[#0f2f20]" class="btn rounded-full border-0 bg-[#123824] px-6 text-white hover:bg-[#0f2f20]"
> >
Добавить Добавить
</NuxtLink> </NuxtLink>
</div> </template>
</UiBackHeader>
<div <div
v-if="addressFeedback" v-if="addressFeedback"

View File

@@ -117,14 +117,12 @@ onBeforeUnmount(() => {
<template> <template>
<section class="space-y-6"> <section class="space-y-6">
<NuxtLink to="/profile/addresses" class="link link-hover text-sm"> Назад к адресам</NuxtLink> <UiBackHeader
to="/profile/addresses"
<div class="space-y-2"> back-label="Назад к адресам"
<h1 class="text-3xl font-extrabold text-[#0f2f20]">Новый адрес</h1> title="Новый адрес"
<p class="text-sm leading-6 text-[#466653]"> subtitle="Найдите адрес через DaData и сохраните его в профиль."
Найдите адрес через DaData и сохраните его в профиль. />
</p>
</div>
<div class="rounded-[28px] bg-white px-5 py-5 shadow-[0_18px_38px_rgba(18,56,36,0.08)] md:px-6 md:py-6"> <div class="rounded-[28px] bg-white px-5 py-5 shadow-[0_18px_38px_rgba(18,56,36,0.08)] md:px-6 md:py-6">
<fieldset class="fieldset"> <fieldset class="fieldset">

View File

@@ -254,23 +254,21 @@ onBeforeUnmount(() => {
<template> <template>
<section class="space-y-6"> <section class="space-y-6">
<NuxtLink to="/profile" class="link link-hover text-sm"> Назад в профиль</NuxtLink> <UiBackHeader
to="/profile"
<div class="space-y-2"> back-label="Назад в профиль"
<div class="flex flex-wrap items-center justify-between gap-3"> title="Карточка контрагента"
<h1 class="text-3xl font-extrabold text-[#0f2f20]">Карточка контрагента</h1> subtitle="Заполните реквизиты компании, банка и подписанта. После этого карточку можно будет использовать в заказах без ручного добивания данных."
>
<template #actions>
<span <span
class="inline-flex items-center rounded-full px-3 py-1 text-xs font-bold uppercase tracking-[0.16em]" class="inline-flex items-center rounded-full px-3 py-1 text-xs font-bold uppercase tracking-[0.16em]"
:class="profileIsComplete ? 'bg-[#e8f6ee] text-[#0d854a]' : 'bg-[#fff3dc] text-[#b06b00]'" :class="profileIsComplete ? 'bg-[#e8f6ee] text-[#0d854a]' : 'bg-[#fff3dc] text-[#b06b00]'"
> >
{{ profileIsComplete ? 'Заполнено' : 'Нужно заполнить' }} {{ profileIsComplete ? 'Заполнено' : 'Нужно заполнить' }}
</span> </span>
</div> </template>
</UiBackHeader>
<p class="max-w-3xl text-sm leading-6 text-[#355947]">
Заполните реквизиты компании, банка и подписанта. После этого карточку можно будет использовать в заказах без ручного добивания данных.
</p>
</div>
<div class="space-y-4"> <div class="space-y-4">
<section class="surface-card rounded-3xl p-5 md:p-6"> <section class="surface-card rounded-3xl p-5 md:p-6">

View File

@@ -134,14 +134,12 @@ async function removeConnection(connectionId: string) {
<template> <template>
<section class="space-y-6"> <section class="space-y-6">
<NuxtLink to="/profile" class="link link-hover text-sm"> Назад в профиль</NuxtLink> <UiBackHeader
to="/profile"
<div class="space-y-2"> back-label="Назад в профиль"
<h1 class="text-3xl font-extrabold text-[#0f2f20]">Уведомления</h1> title="Уведомления"
<p class="text-sm leading-6 text-[#466653]"> subtitle="Подключите мессенджер, чтобы получать уведомления по заказам."
Подключите мессенджер, чтобы получать уведомления по заказам. />
</p>
</div>
<div <div
v-if="feedback" v-if="feedback"