Banner: system banners zone carousel mobile layout remove Home from Profile
This commit is contained in:
@@ -52,10 +52,10 @@
|
||||
}
|
||||
|
||||
.hello-hero-body {
|
||||
padding-top: 8px;
|
||||
min-height: 140px;
|
||||
padding: 8px 20px 16px;
|
||||
min-height: 120px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
@@ -64,6 +64,56 @@
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.hello-hero-success {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.hello-hero-success-main {
|
||||
flex: 1 1 auto;
|
||||
font-size: 14px;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.hello-hero-system {
|
||||
flex: 0 0 260px;
|
||||
max-width: 260px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.hello-hero-system :where(.ant-alert) {
|
||||
padding: 8px 12px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.hello-hero-system :where(.ant-alert-content) {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.hello-hero-system :where(.ant-alert-message) {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
white-space: normal;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.hello-hero-system :where(.ant-alert-description) {
|
||||
font-size: 12px;
|
||||
white-space: normal;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.hello-hero-system-carousel .hello-hero-system-slide {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.hello-grid {
|
||||
margin-top: 32px;
|
||||
}
|
||||
@@ -204,6 +254,32 @@
|
||||
--tile-h: 140px;
|
||||
}
|
||||
|
||||
.hello-hero-success {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.hello-hero-system {
|
||||
flex: 1 1 auto;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* На узких экранах баннер на всю ширину, кнопка под текстом — текст не сжимается */
|
||||
.hello-hero-system :where(.ant-alert) {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.hello-hero-system :where(.ant-alert-action) {
|
||||
flex-basis: 100%;
|
||||
order: 1;
|
||||
margin-top: 8px;
|
||||
margin-left: 0;
|
||||
}
|
||||
.hello-hero-system :where(.ant-alert-close-icon) {
|
||||
order: 0;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.tile-card :where(.ant-card-body) {
|
||||
padding: 16px 12px;
|
||||
gap: 8px;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Card, Button, Input, Space, Spin, message, Row, Col } from 'antd';
|
||||
import { Card, Button, Input, Space, Spin, message, Row, Col, Alert, Carousel } from 'antd';
|
||||
import {
|
||||
User,
|
||||
IdCard,
|
||||
@@ -22,6 +22,7 @@ type Status = 'idle' | 'loading' | 'success' | 'error';
|
||||
interface HelloAuthProps {
|
||||
onAvatarChange?: (url: string) => void;
|
||||
onNavigate?: (path: string) => void;
|
||||
onProfileNeedsAttentionChange?: (value: boolean) => void;
|
||||
}
|
||||
|
||||
const INIT_DATA_WAIT_MS = 5500;
|
||||
@@ -29,11 +30,19 @@ const INIT_DATA_POLL_MS = 200;
|
||||
const INIT_DATA_BG_RECOVERY_MS = 15000;
|
||||
const INIT_DATA_BG_TICK_MS = 250;
|
||||
|
||||
export default function HelloAuth({ onAvatarChange, onNavigate }: HelloAuthProps) {
|
||||
export default function HelloAuth({ onAvatarChange, onNavigate, onProfileNeedsAttentionChange }: HelloAuthProps) {
|
||||
const [status, setStatus] = useState<Status>('idle');
|
||||
const [greeting, setGreeting] = useState<string>('Привет!');
|
||||
const [error, setError] = useState<string>('');
|
||||
const [avatar, setAvatar] = useState<string>('');
|
||||
const [profileNeedsAttention, setProfileNeedsAttention] = useState<boolean>(false);
|
||||
const [profileBannerDismissed, setProfileBannerDismissed] = useState<boolean>(() => {
|
||||
try {
|
||||
return sessionStorage.getItem('profile_attention_banner_dismissed_v1') === '1';
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
const [phone, setPhone] = useState<string>('');
|
||||
const [code, setCode] = useState<string>('');
|
||||
const [codeSent, setCodeSent] = useState<boolean>(false);
|
||||
@@ -97,6 +106,15 @@ export default function HelloAuth({ onAvatarChange, onNavigate }: HelloAuthProps
|
||||
return 'need_contact';
|
||||
}
|
||||
if (res.ok && data.success) {
|
||||
const needsAttentionRaw =
|
||||
(data.profile_needs_attention as unknown) ??
|
||||
(data.profileNeedsAttention as unknown) ??
|
||||
(data.need_profile_confirm as unknown) ??
|
||||
(data.needProfileConfirm as unknown);
|
||||
const needsAttention = needsAttentionRaw === true || needsAttentionRaw === 1 || needsAttentionRaw === '1' || needsAttentionRaw === 'true';
|
||||
setProfileNeedsAttention(needsAttention);
|
||||
onProfileNeedsAttentionChange?.(needsAttention);
|
||||
|
||||
const token = data.session_token as string | undefined;
|
||||
if (token) {
|
||||
try {
|
||||
@@ -171,6 +189,15 @@ export default function HelloAuth({ onAvatarChange, onNavigate }: HelloAuthProps
|
||||
});
|
||||
const verifyData = await verifyRes.json().catch(() => ({}));
|
||||
if (verifyData?.valid === true) {
|
||||
const needsAttentionRaw =
|
||||
verifyData.profile_needs_attention ??
|
||||
verifyData.profileNeedsAttention ??
|
||||
verifyData.need_profile_confirm ??
|
||||
verifyData.needProfileConfirm;
|
||||
const needsAttention = needsAttentionRaw === true || needsAttentionRaw === 1 || needsAttentionRaw === '1' || needsAttentionRaw === 'true';
|
||||
setProfileNeedsAttention(needsAttention);
|
||||
onProfileNeedsAttentionChange?.(needsAttention);
|
||||
|
||||
setGreeting('Привет!');
|
||||
const tgUser = (window as any).Telegram?.WebApp?.initDataUnsafe?.user;
|
||||
const maxUser = (window as any).WebApp?.initDataUnsafe?.user;
|
||||
@@ -260,7 +287,7 @@ export default function HelloAuth({ onAvatarChange, onNavigate }: HelloAuthProps
|
||||
};
|
||||
|
||||
tryAuth();
|
||||
}, [onAvatarChange, onNavigate]);
|
||||
}, [onAvatarChange, onNavigate, onProfileNeedsAttentionChange]);
|
||||
|
||||
if (noInitDataAfterTimeout && status === 'idle') {
|
||||
return (
|
||||
@@ -325,6 +352,43 @@ if (data.avatar_url) {
|
||||
}
|
||||
};
|
||||
|
||||
const systemBanners: Array<{ key: string; node: JSX.Element }> = [];
|
||||
|
||||
if (status === 'success' && profileNeedsAttention && !profileBannerDismissed) {
|
||||
systemBanners.push({
|
||||
key: 'profile-not-complete',
|
||||
node: (
|
||||
<Alert
|
||||
type="warning"
|
||||
showIcon
|
||||
closable
|
||||
message="Профиль не заполнен"
|
||||
description="Пожалуйста, заполните обязательные поля профиля, чтобы продолжить работу."
|
||||
onClose={() => {
|
||||
try {
|
||||
sessionStorage.setItem('profile_attention_banner_dismissed_v1', '1');
|
||||
} catch (_) {}
|
||||
setProfileBannerDismissed(true);
|
||||
}}
|
||||
action={
|
||||
<Button
|
||||
size="small"
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
if (onNavigate) onNavigate('/profile');
|
||||
else window.location.href = '/profile';
|
||||
}}
|
||||
>
|
||||
Заполнить профиль
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
const hasSystemBanners = systemBanners.length > 0;
|
||||
|
||||
const tiles: Array<{ title: string; icon: typeof User; color: string; href?: string }> = [
|
||||
{ title: 'Мои обращения', icon: ClipboardList, color: '#6366F1', href: '/' },
|
||||
{ title: 'Подать жалобу', icon: FileWarning, color: '#EA580C', href: '/new' },
|
||||
@@ -359,7 +423,31 @@ if (data.avatar_url) {
|
||||
{status === 'loading' ? (
|
||||
<Spin size="large" tip="Авторизация..." />
|
||||
) : status === 'success' ? (
|
||||
<p>Теперь ты в системе — можно продолжать</p>
|
||||
<div className={`hello-hero-success${hasSystemBanners ? ' hello-hero-success--with-banner' : ''}`}>
|
||||
<div className="hello-hero-success-main">
|
||||
<p>Теперь ты в системе — можно продолжать</p>
|
||||
</div>
|
||||
{hasSystemBanners && (
|
||||
<div className="hello-hero-system">
|
||||
{systemBanners.length === 1 ? (
|
||||
systemBanners[0].node
|
||||
) : (
|
||||
<Carousel
|
||||
autoplay
|
||||
dots
|
||||
adaptiveHeight
|
||||
className="hello-hero-system-carousel"
|
||||
>
|
||||
{systemBanners.map((banner) => (
|
||||
<div key={banner.key} className="hello-hero-system-slide">
|
||||
{banner.node}
|
||||
</div>
|
||||
))}
|
||||
</Carousel>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : status === 'error' ? (
|
||||
<p className="hello-hero-error">{error}</p>
|
||||
) : (
|
||||
|
||||
@@ -207,7 +207,6 @@ export default function Profile({ onNavigate }: ProfileProps) {
|
||||
<Card
|
||||
className="profile-card"
|
||||
title={<><User size={20} style={{ marginRight: 8, verticalAlign: 'middle' }} /> Профиль</>}
|
||||
extra={onNavigate ? <Button type="link" onClick={() => onNavigate('/')}>Домой</Button> : null}
|
||||
>
|
||||
<Form form={form} layout="vertical" onFinish={handleSave}>
|
||||
{PROFILE_FIELDS.map(({ key, keys, label, editable }) => (
|
||||
|
||||
Reference in New Issue
Block a user