change design
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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)} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
),
|
||||
};
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user