From e80ae8ac540cfd168913b940346e35c4ec8f62c5 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 2 Oct 2026 14:24:06 +0000 Subject: [PATCH] feat: open Community from the About menu on mobile The Community dialog had no entry in the landing or app menus, so it could not be opened from the mobile navigation. Add it under About in both menus. Co-authored-by: David --- .../communitydialog.module.css | 70 +++++++++++++++ components/CommunityDialog/index.tsx | 73 +++++++++++++++ components/LandingPage/Navbar.tsx | 40 ++++++++- components/Sidebar/index.tsx | 34 +++++++ components/Sidebar/sidebar.module.css | 39 ++++++++ styles/landing.module.css | 90 +++++++++++++++++++ 6 files changed, 345 insertions(+), 1 deletion(-) create mode 100644 components/CommunityDialog/communitydialog.module.css create mode 100644 components/CommunityDialog/index.tsx diff --git a/components/CommunityDialog/communitydialog.module.css b/components/CommunityDialog/communitydialog.module.css new file mode 100644 index 00000000..eba83ac9 --- /dev/null +++ b/components/CommunityDialog/communitydialog.module.css @@ -0,0 +1,70 @@ +.backdrop { + position: fixed; + inset: 0; + z-index: 100000; + display: flex; + align-items: center; + justify-content: center; + padding: 20px; + background: rgba(0, 0, 0, 0.55); +} + +.dialog { + width: 100%; + max-width: 420px; + background: var(--secondary-bg-color); + color: var(--primary-text-color); + border-radius: 12px; + padding: 28px 24px 22px; + box-shadow: 0 16px 40px rgba(0, 0, 0, 0.28); +} + +.dialog h2 { + font-size: 28px; + margin-bottom: 12px; +} + +.dialog p { + margin: 0 0 20px; + color: var(--secondary-text-color); + line-height: 1.5; +} + +.links { + display: flex; + flex-direction: column; + gap: 10px; +} + +.links a { + display: block; + padding: 12px 14px; + border: 1px solid var(--border-color); + border-radius: 10px; + color: var(--primary-text-color); + text-decoration: none; + font-weight: 500; +} + +.links a:hover { + border-color: var(--accent-color); + color: var(--accent-color); +} + +button.close { + margin-top: 18px; + width: 100%; + border-radius: 10px; + padding: 10px 16px; + border: 1px solid var(--primary-text-color); + background: transparent !important; + color: var(--primary-text-color); + font-weight: 500; + cursor: pointer; +} + +button.close:enabled:hover { + background: var(--accent-color) !important; + border-color: var(--accent-color) !important; + color: #fff; +} diff --git a/components/CommunityDialog/index.tsx b/components/CommunityDialog/index.tsx new file mode 100644 index 00000000..b79858c0 --- /dev/null +++ b/components/CommunityDialog/index.tsx @@ -0,0 +1,73 @@ +import { useEffect } from 'react' +import style from './communitydialog.module.css' + +interface CommunityDialogProps { + open: boolean + onClose: () => void +} + +const COMMUNITY_LINKS = [ + { + label: 'Telegram', + href: 'https://t.me/paybutton' + }, + { + label: 'X', + href: 'https://x.com/thepaybutton' + }, + { + label: 'GitHub', + href: 'https://github.com/paybutton' + } +] + +export default function CommunityDialog ({ open, onClose }: CommunityDialogProps): JSX.Element | null { + useEffect(() => { + if (!open) return undefined + const onKeyDown = (event: KeyboardEvent): void => { + if (event.key === 'Escape') onClose() + } + document.addEventListener('keydown', onKeyDown) + const previousOverflow = document.body.style.overflow + document.body.style.overflow = 'hidden' + return () => { + document.removeEventListener('keydown', onKeyDown) + document.body.style.overflow = previousOverflow + } + }, [open, onClose]) + + if (!open) return null + + return ( +
+
event.stopPropagation()} + > +

Community

+

+ PayButton is a community-driven open-source project. Join the + conversation, follow updates, and contribute. +

+
+ {COMMUNITY_LINKS.map((link) => ( + + {link.label} + + ))} +
+ +
+
+ ) +} diff --git a/components/LandingPage/Navbar.tsx b/components/LandingPage/Navbar.tsx index 2fe93e9e..0f28a011 100644 --- a/components/LandingPage/Navbar.tsx +++ b/components/LandingPage/Navbar.tsx @@ -1,10 +1,11 @@ -import { useState } from 'react' +import { useEffect, useState } from 'react' import Link from 'next/link' import Image from 'next/image' import style from 'styles/landing.module.css' import logoImageSource from 'assets/logo.png' import dynamic from 'next/dynamic' import LogoutButton from './LogoutButton' +import CommunityDialog from 'components/CommunityDialog' const ThemeToggle = dynamic( async () => await import('components/Sidebar/themetoggle'), @@ -19,6 +20,24 @@ interface IProps { export default function Navbar ({ userId }: IProps): JSX.Element { const [mobileMenu, setMobileMenu] = useState(false) + const [aboutOpen, setAboutOpen] = useState(false) + const [communityOpen, setCommunityOpen] = useState(false) + + useEffect(() => { + if (!aboutOpen) return undefined + const closeAbout = (event: MouseEvent): void => { + const target = event.target as HTMLElement + if (target.closest('[data-about-menu]') === null) setAboutOpen(false) + } + document.addEventListener('mousedown', closeAbout) + return () => document.removeEventListener('mousedown', closeAbout) + }, [aboutOpen]) + + const openCommunity = (): void => { + setAboutOpen(false) + setMobileMenu(false) + setCommunityOpen(true) + } return (
@@ -71,6 +90,24 @@ export default function Navbar ({ userId }: IProps): JSX.Element { > Docs +
+ + {aboutOpen && ( +
+ +
+ )} +
{userId === undefined ? ( <> @@ -96,6 +133,7 @@ export default function Navbar ({ userId }: IProps): JSX.Element { )}
+ setCommunityOpen(false)} /> ) } diff --git a/components/Sidebar/index.tsx b/components/Sidebar/index.tsx index 766d9d24..876b1204 100644 --- a/components/Sidebar/index.tsx +++ b/components/Sidebar/index.tsx @@ -18,6 +18,7 @@ import Docs from 'assets/docs.png' import Admin from 'assets/admin-icon.png' import Logout from 'assets/logout-icon.png' import { UserProfile } from '@prisma/client' +import CommunityDialog from 'components/CommunityDialog' const ThemeToggle = dynamic(async () => await import('./themetoggle'), { ssr: false }) @@ -74,6 +75,8 @@ interface IProps { const Sidebar: React.FC = ({ chart, setChart, loggedUser }: IProps) => { const [menu, setMenu] = useState(false) + const [aboutOpen, setAboutOpen] = useState(false) + const [communityOpen, setCommunityOpen] = useState(false) const useMediaQuery = (width: number): boolean => { const [targetReached, setTargetReached] = useState(false) const updateTarget = useCallback((e) => { @@ -139,6 +142,36 @@ const Sidebar: React.FC = ({ chart, setChart, loggedUser }: IProps) => { .map(item => )} +
  • + + {aboutOpen && ( + + )} +
  • @@ -162,6 +195,7 @@ const Sidebar: React.FC = ({ chart, setChart, loggedUser }: IProps) => { + setCommunityOpen(false)} /> } diff --git a/components/Sidebar/sidebar.module.css b/components/Sidebar/sidebar.module.css index c710134e..5bc57a2f 100644 --- a/components/Sidebar/sidebar.module.css +++ b/components/Sidebar/sidebar.module.css @@ -17,6 +17,40 @@ padding: 0; } +.about_group { + list-style: none; +} + +button.about_btn, +button.community_btn { + display: flex; + align-items: center; + width: 100%; + cursor: pointer; + font-weight: 500; + font-family: inherit; + font-size: inherit; + text-align: left; + padding: 8px 25px; + border: none; + border-radius: 10px; + background: transparent !important; + color: var(--primary-text-color); + box-shadow: none; +} + +button.community_btn { + padding-left: 40px; +} + +button.about_btn:enabled:hover, +button.community_btn:enabled:hover { + background: var(--primary-text-color) !important; + border-color: transparent !important; + color: var(--secondary-bg-color); + filter: none; +} + .section { padding: 0; margin: 0; @@ -144,6 +178,11 @@ body[data-theme='dark'] .socialctn .moon { } @media (max-width: 960px) { + button.about_btn, + button.community_btn { + border-radius: 0; + } + .topmenu { background-color: var(--secondary-bg-color); height: 50px; diff --git a/styles/landing.module.css b/styles/landing.module.css index f7a8a2dd..4710bcbc 100644 --- a/styles/landing.module.css +++ b/styles/landing.module.css @@ -131,6 +131,66 @@ body[data-theme="dark"] .navbar_ctn img { margin-right: 0px; } +.about_wrap { + position: relative; + margin-left: 30px; +} + +button.about_btn { + background: transparent !important; + border: none; + padding: 0; + color: var(--primary-text-color); + font-family: inherit; + font-size: inherit; + font-weight: 500; + cursor: pointer; + box-shadow: none; +} + +button.about_btn:enabled:hover, +button.about_btn:enabled:focus { + background: transparent !important; + border-color: transparent !important; + color: var(--accent-color); + filter: none; +} + +.about_dropdown { + position: absolute; + top: calc(100% + 14px); + left: 0; + min-width: 160px; + background: var(--secondary-bg-color); + border: 1px solid var(--border-color); + border-radius: 10px; + padding: 6px; + z-index: 5; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12); +} + +.about_dropdown button { + display: block; + width: 100%; + text-align: left; + background: transparent !important; + border: none; + border-radius: 8px; + padding: 8px 12px; + color: var(--primary-text-color); + font-family: inherit; + font-size: 15px; + font-weight: 500; + cursor: pointer; + box-shadow: none; +} + +.about_dropdown button:enabled:hover { + background: transparent !important; + border-color: transparent !important; + color: var(--accent-color); +} + .menubtn_ctn_outer { display: none; } @@ -176,6 +236,36 @@ body[data-theme="dark"] .navbar_ctn img { order: 8; } + .about_wrap { + order: 0; + margin-left: 0; + width: 100%; + } + + button.about_btn { + font-size: 16px; + padding: 10px 0; + border-bottom: 1px solid var(--primary-text-color); + width: 100%; + text-align: left; + border-radius: 0; + } + + .about_dropdown { + position: static; + min-width: 0; + border: none; + box-shadow: none; + background: transparent; + padding: 0 0 0 12px; + } + + .about_dropdown button { + padding: 10px 0; + border-bottom: 1px solid var(--primary-text-color); + border-radius: 0; + } + .navbar_ctn img { width: 120px; }