change design

This commit is contained in:
SlimusMinus
2026-08-17 23:17:30 +03:00
parent ed92e45f84
commit f02b25e8f3
24 changed files with 1199 additions and 799 deletions

View File

@@ -98,10 +98,10 @@ function LoginFormInline({ saveAuth, onClose, onSwitch }) {
<SubmitButton loading={loading} label={t('auth.login')} />
<p style={{textAlign: "center", marginTop: 14, color: "rgba(255,255,255,.45)", fontSize: ".85rem"}}>
<p style={{textAlign: "center", marginTop: 14, color: "var(--muted)", fontSize: ".85rem"}}>
{t('auth.noAccount')}{" "}
<button onClick={onSwitch}
style={{background: "none", border: "none", color: "#2de0c8", cursor: "pointer", fontWeight: 700, fontSize: ".85rem", fontFamily: "var(--font)",}}
style={{background: "none", border: "none", color: "var(--teal)", cursor: "pointer", fontWeight: 700, fontSize: ".85rem", fontFamily: "var(--font)",}}
>
{t('auth.register')}
</button>
@@ -216,10 +216,10 @@ function RegisterFormInline({ saveAuth, onClose, onSwitch }) {
<SubmitButton loading={loading} label={t('auth.createAccountBtn')} />
<p style={{textAlign: "center", marginTop: 14, color: "rgba(255,255,255,.45)", fontSize: ".85rem"}}>
<p style={{textAlign: "center", marginTop: 14, color: "var(--muted)", fontSize: ".85rem"}}>
{t('auth.hasAccount')}{" "}
<button onClick={onSwitch}
style={{background: "none", border: "none", color: "#2de0c8", cursor: "pointer", fontWeight: 700, fontSize: ".85rem", fontFamily: "var(--font)",}}>
style={{background: "none", border: "none", color: "var(--teal)", cursor: "pointer", fontWeight: 700, fontSize: ".85rem", fontFamily: "var(--font)",}}>
{t('auth.loginLink')}
</button>
</p>

View File

@@ -1,14 +1,89 @@
import { useState } from "react";
import { useTranslation } from 'react-i18next';
import ProfileAvatar from "./profile/ProfileAvatar";
export default function RightSidebar() {
function FollowButton() {
const [following, setFollowing] = useState(false);
const { t } = useTranslation();
return (
<button className="f-right-action-btn" onClick={() => setFollowing(f => !f)}>
{following ? t('rightSidebar.following') : t('rightSidebar.subscribe')}
</button>
);
}
export default function RightSidebar({ ads, profileData, isAuthenticated, onProfileClick, onLoginClick, onNavClick }) {
const { t } = useTranslation();
const suggestions = [...(ads || [])]
.sort((a, b) => (b.likeCount || 0) - (a.likeCount || 0))
.slice(0, 4);
const profileName = isAuthenticated
? `${profileData?.firstName || ""} ${profileData?.lastName || ""}`.trim() || profileData?.login
: null;
return (
<div className="f-right">
<div className="f-right-label">{t('rightSidebar.advertisement')}</div>
<div className="f-right-empty">
<div className="f-right-empty-icon">📢</div>
<div className="f-right-empty-text" dangerouslySetInnerHTML={{ __html: t('rightSidebar.emptyText') }} />
<button className="f-add-promo-btn">{t('rightSidebar.addAd')}</button>
<div className="f-right-profile">
{isAuthenticated ? (
<>
<ProfileAvatar avatar={profileData?.avatar} name={profileName} />
<div className="f-right-profile-info">
<div className="f-right-profile-name">{profileName}</div>
<div className="f-right-profile-sub">{profileData?.login}</div>
</div>
<button className="f-right-action-btn" onClick={onProfileClick}>
{t('rightSidebar.yourProfile')}
</button>
</>
) : (
<>
<div className="f-ig-ring">
<div className="f-ig-avatar">👤</div>
</div>
<div className="f-right-profile-info">
<div className="f-right-profile-name">{t('rightSidebar.guest')}</div>
<div className="f-right-profile-sub">{t('rightSidebar.guestSub')}</div>
</div>
<button className="f-right-action-btn" onClick={onLoginClick}>
{t('feed.login')}
</button>
</>
)}
</div>
<div className="f-right-header">
<span className="f-right-label">{t('rightSidebar.suggestions')}</span>
<button className="f-right-link" onClick={() => onNavClick?.("home")}>
{t('rightSidebar.all')}
</button>
</div>
{suggestions.length === 0 && (
<div className="f-right-suggest-sub">{t('rightSidebar.empty')}</div>
)}
{suggestions.map(ad => (
<div className="f-right-suggest" key={ad.id}>
<div className="f-ig-ring f-ig-ring-sm">
<div className="f-ig-avatar">{ad.emoji || "📋"}</div>
</div>
<div className="f-right-suggest-info">
<div className="f-right-suggest-name">{ad.title}</div>
<div className="f-right-suggest-sub">
{ad.city || (ad.type === "loss" ? t('postCard.loss') : t('postCard.found'))}
</div>
</div>
<FollowButton />
</div>
))}
<div className="f-right-foot">
<span>© {new Date().getFullYear()} {t('app.name')}</span>
<span>{t('rightSidebar.footRules')}</span>
<span>{t('rightSidebar.footPrivacy')}</span>
<span>{t('rightSidebar.footSupport')}</span>
</div>
</div>
);

View File

@@ -11,6 +11,7 @@ import { useUnread } from "../context/UnreadContext";
import { useLanguageToggle } from "../hooks/useLanguageToggle";
import { useTranslation } from 'react-i18next';
import SupportModal from "./SupportModal";
import ProfileAvatar from "./profile/ProfileAvatar";
export default function Sidebar({
open,
@@ -21,6 +22,9 @@ export default function Sidebar({
onLoginClick,
onRulesClick,
onReviewsClick,
onCreatePost,
onProfileClick,
profileData,
}) {
const navigate = useNavigate();
const { theme, toggle: toggleTheme } = useTheme();
@@ -28,6 +32,35 @@ export default function Sidebar({
const { t } = useTranslation();
const { language, toggleLanguage } = useLanguageToggle();
const [supportOpen, setSupportOpen] = useState(false);
const HelpIcon = NavIcons.help;
const handleNavItem = (itemKey) => {
if (!isAuthenticated && itemKey === "myads") {
onLoginClick();
return;
}
if (itemKey === "chat") {
if (!isAuthenticated) {
onLoginClick();
} else {
navigate("/chats");
}
return;
}
if (itemKey === "create") {
onCreatePost?.();
return;
}
onNavClick(itemKey);
};
const handleProfileClick = () => {
if (!isAuthenticated) {
onLoginClick();
} else {
onProfileClick?.();
}
};
return (
<>
@@ -40,27 +73,15 @@ export default function Sidebar({
<nav className="f-sb-nav">
{NAV_ITEMS.map(({ icon, key: itemKey, labelKey }) => {
const Ic = NavIcons[icon];
const handleClick = () => {
// «Мои объявления» требует авторизации
if (!isAuthenticated && itemKey === "myads") {
onLoginClick();
} else if (itemKey === "chat") {
if (!isAuthenticated) {
onLoginClick();
} else {
navigate("/chats");
}
} else {
onNavClick(itemKey);
}
onClose();
};
return (
<button
type="button"
key={itemKey}
className={`f-sb-item${activeNav === itemKey ? " active" : ""}`}
onClick={handleClick}
className={`f-sb-item${activeNav === itemKey ? " active" : ""}${itemKey === "create" ? " f-sb-create" : ""}`}
onClick={() => {
handleNavItem(itemKey);
onClose();
}}
>
<Ic /> {t(labelKey)}
{itemKey === "chat" && totalUnread > 0 && (
@@ -99,20 +120,40 @@ export default function Sidebar({
);
})}
<button type="button" className="f-sb-item" onClick={() => setSupportOpen(true)}>
<NavIcons.help /> {t('nav.support')}
</button>
<HelpIcon /> {t('nav.support')} </button>
<div className="f-sb-divider" />
<button type="button" className="f-sb-item f-sb-theme-toggle" onClick={toggleTheme}>
<button type="button" className="f-sb-item" onClick={toggleTheme}>
{theme === "dark" ? <SunIcon /> : <MoonIcon />}
{theme === "dark" ? t('sidebar.lightTheme') : t('sidebar.darkTheme')}
</button>
<button type="button" className="f-sb-item f-sb-lang-toggle" onClick={toggleLanguage}>
<button type="button" className="f-sb-item" onClick={toggleLanguage}>
<GlobeIcon />
{language === 'ru' ? 'English' : 'Русский'}
</button>
<div className="f-sb-divider" />
<button type="button" className="f-sb-profile" onClick={handleProfileClick}>
{isAuthenticated ? (
<ProfileAvatar
avatar={profileData?.avatar}
name={`${profileData?.firstName || ""} ${profileData?.lastName || ""}`}
/>
) : (
<div className="f-profile-avatar-placeholder">👤</div>
)}
<div className="f-sb-profile-info">
<div className="f-sb-profile-name">
{isAuthenticated
? `${profileData?.firstName || ""} ${profileData?.lastName || ""}`.trim() || profileData?.login
: t('feed.login')}
</div>
<div className="f-sb-profile-login">
{isAuthenticated ? profileData?.login || t('profile.default') : t('auth.noAccount')}
</div>
</div>
</button>
</div>
</div>
{supportOpen && <SupportModal onClose={() => setSupportOpen(false)} />}
</>
);
}
}

View File

@@ -2,6 +2,7 @@ import { useRef } from "react";
import Picker from "@emoji-mart/react";
import emojiData from "@emoji-mart/data";
import { useClickOutside } from "../../hooks/useClickOutside";
import { useTheme } from "../../context/ThemeContext";
export default function ChatInputBar({
input,
@@ -16,6 +17,7 @@ export default function ChatInputBar({
}) {
const emojiPickerRef = useRef(null);
useClickOutside(emojiPickerRef, onCloseEmoji, showEmoji);
const { theme } = useTheme();
return (
<div className="chat-input-area">
@@ -24,7 +26,7 @@ export default function ChatInputBar({
<Picker
data={emojiData}
onEmojiSelect={onEmojiSelect}
theme="dark"
theme={theme}
previewPosition="none"
skinTonePosition="none"
searchPosition="sticky"

View File

@@ -1,28 +1,10 @@
import AppTopBar from "../layout/AppTopBar";
export default function FeedTopBar({
onMenuClick,
onCreatePost,
isAuthenticated,
profileData,
onProfileClick,
onLoginClick,
t,
}) {
export default function FeedTopBar({ onMenuClick }) {
return (
<AppTopBar
onMenuClick={onMenuClick}
isAuthenticated={isAuthenticated}
profileData={profileData}
onProfileClick={onProfileClick}
onLoginClick={onLoginClick}
loginLabel={t('feed.login')}
centerContent={
<button className="f-post-btn" onClick={onCreatePost}>
<span style={{ fontSize: "0.9rem", fontWeight: 900, lineHeight: 1 }}>+</span>
{t('feed.createPost')}
</button>
}
hideProfile
/>
);
}
}

View File

@@ -51,4 +51,11 @@ export const NavIcons = {
<line x1="12" y1="17" x2="12.01" y2="17" />
</svg>
),
plus: () => (
<svg width={20} height={20} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2.4} strokeLinecap="round" strokeLinejoin="round">
<rect x="3" y="3" width="18" height="18" rx="6" />
<line x1="12" y1="8" x2="12" y2="16" />
<line x1="8" y1="12" x2="16" y2="12" />
</svg>
),
};

View File

@@ -1,9 +1,10 @@
import MenuIcon from "../icons/MenuIcon";
import ProfileAvatar from "../profile/ProfileAvatar";
import { useTranslation } from 'react-i18next';
/**
* Общая верхняя панель приложения: бургер-меню слева,
* произвольный контент по центру, профиль/логин справа.
* Общая верхняя панель приложения (мобильная версия Instagram-style):
* бургер-меню слева, название сервиса, контент по центру, профиль/логин справа.
*/
export default function AppTopBar({
onMenuClick,
@@ -13,14 +14,19 @@ export default function AppTopBar({
onProfileClick,
onLoginClick,
loginLabel,
hideProfile = false,
}) {
const { t } = useTranslation();
return (
<div className="f-topbar">
<button className="f-menu-btn" onClick={onMenuClick}>
<MenuIcon />
</button>
<span className="f-topbar-wordmark">{t('app.name')}</span>
<div className="f-topbar-center">{centerContent}</div>
{isAuthenticated ? (
{hideProfile ? (
<span className="f-topbar-spacer" />
) : isAuthenticated ? (
<div className="f-topbar-profile" onClick={onProfileClick}>
<ProfileAvatar
avatar={profileData.avatar}
@@ -34,4 +40,4 @@ export default function AppTopBar({
)}
</div>
);
}
}