diff --git a/web/app/(app)/contribute/page.tsx b/web/app/(app)/contribute/page.tsx
new file mode 100644
index 0000000..ada61c5
--- /dev/null
+++ b/web/app/(app)/contribute/page.tsx
@@ -0,0 +1,290 @@
+'use client'
+
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardHeader,
+ CardTitle,
+} from '@/components/ui/card'
+import { Button } from '@/components/ui/button'
+import {
+ Bitcoin,
+ CircleDollarSign,
+ Copy,
+ Github,
+ Heart,
+ MessageSquare,
+ Star,
+ Wallet,
+ Shield,
+ Coins,
+} from 'lucide-react'
+import Link from 'next/link'
+import { ExternalLinks } from '@/config/external-links'
+import { useToast } from '@/hooks/use-toast'
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from '@/components/ui/dialog'
+
+const cryptoWallets = [
+ {
+ name: 'Bitcoin (BTC)',
+ address: 'bc1qhffsnhp8ynqy6xvh982cu0x5w7vguuum3nqae9',
+ network: 'Bitcoin',
+ },
+ {
+ name: 'Ethereum (ETH)',
+ address: '0xDB8560a42bdaa42C58462C6b2ee5A7D36F1c1f2a',
+ network: 'Ethereum (ERC20)',
+ },
+ {
+ name: 'Tether (USDT)',
+ address: '0xDB8560a42bdaa42C58462C6b2ee5A7D36F1c1f2a',
+ network: 'Ethereum (ERC20)',
+ },
+ // {
+ // name: 'Tether (USDT)',
+ // address: 'TD6txzY61D6EgnVfMLPsqKhYfyV5iHrbkw',
+ // network: 'Tron (TRC20)',
+ // },
+ {
+ name: 'Monero (XMR)',
+ address:
+ '856J5eHJM7bgBhkc51oCuMYUGKvUvF1zwAWrQsqwuH1shG9qnX4YkoZbMmhCPep1JragY2W1hpzAnDda6BXvCgZxUJhUyTg',
+ network: 'Monero (XMR)',
+ },
+]
+
+export default function ContributePage() {
+ const { toast } = useToast()
+
+ const handleCopy = (text: string, type: string) => {
+ navigator.clipboard.writeText(text)
+ toast({
+ title: `${type} address copied to clipboard`,
+ })
+ }
+
+ return (
+
+
+
Support TextBee
+
+ Your contribution, whether financial or through code, helps keep this
+ project alive and growing.
+
+
+
+
+
+
+
+
+ Financial Support
+
+
+ Help sustain TextBee's development through financial
+ contributions
+
+
+
+
+
+
+
+ Monthly Support
+
+ Become a patron and support us monthly
+
+
+
+
+
+
+ Support on Patreon
+
+
+
+
+
+
+
+
+ One-time Support
+
+ Make a one-time contribution
+
+
+
+
+
+
+ Donate on Polar
+
+
+
+
+
+
+
+
+ Crypto Donations
+
+ Support us with cryptocurrency
+
+
+
+
+
+
+
+ View Crypto Addresses
+
+
+
+
+
+ Cryptocurrency Donation Addresses
+
+
+
+ {cryptoWallets.map((wallet, index) => (
+
+
+
+ {wallet.name.includes('Bitcoin') ? (
+
+ ) : wallet.name.includes('Ethereum') ? (
+
+ ) : (
+
+ )}{' '}
+ {wallet.name}
+
+
+ handleCopy(wallet.address, wallet.name)
+ }
+ >
+
+
+
+
+ {wallet.address}
+
+
+ Network: {wallet.network}
+
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Code Contributions
+
+
+ Help improve TextBee by contributing to the codebase
+
+
+
+
+
+
+ Star the Project
+
+ Show your support by starring the repository
+
+
+
+
+
+
+ Star on GitHub
+
+
+
+
+
+
+
+ Report Issues
+
+ Help us improve by reporting bugs and suggesting features
+
+
+
+
+
+
+ Create Issue
+
+
+
+
+
+
+
+ Security Reports
+
+ Report security vulnerabilities privately to{' '}
+
+ security@textbee.dev
+
+
+
+
+
+
+
+ Report Vulnerability
+
+
+
+
+
+
+
+
+
+
+
+
+ Join the Community
+
+
+ Connect with other contributors and users
+
+
+
+
+
+
+ Join Discord
+
+
+
+
+
+
+ )
+}
diff --git a/web/app/(app)/dashboard/(components)/community-links.tsx b/web/app/(app)/dashboard/(components)/community-links.tsx
index 00a4e88..6b32b22 100644
--- a/web/app/(app)/dashboard/(components)/community-links.tsx
+++ b/web/app/(app)/dashboard/(components)/community-links.tsx
@@ -6,19 +6,19 @@ import { ExternalLinks } from '@/config/external-links'
export default function CommunityLinks() {
return (
-
+
- GitHub
+ One-time Donation
- Check out our source code and contribute to the project.
+ Support us with a one-time donation of your desired amount.
-
-
-
- View Source
+
+
+
+ Donate Once
@@ -26,7 +26,7 @@ export default function CommunityLinks() {
- Support Us
+ Support on Patreon
@@ -41,6 +41,23 @@ export default function CommunityLinks() {
+
+
+ GitHub
+
+
+
+ Check out our source code and contribute to the project.
+
+
+
+
+ View Source
+
+
+
+
+
Discord
diff --git a/web/app/(app)/dashboard/layout.tsx b/web/app/(app)/dashboard/layout.tsx
index c54e6af..6978711 100644
--- a/web/app/(app)/dashboard/layout.tsx
+++ b/web/app/(app)/dashboard/layout.tsx
@@ -1,9 +1,17 @@
-import Dashboard from "./(components)/dashboard-layout";
+import { JoinCommunityModal } from '@/components/shared/join-community-modal'
+import { ContributeModal } from '@/components/shared/contribute-modal'
+import Dashboard from './(components)/dashboard-layout'
export default function DashboardLayout({
children,
}: {
- children: React.ReactNode;
+ children: React.ReactNode
}) {
- return {children} ;
-}
\ No newline at end of file
+ return (
+
+ {children}
+
+
+
+ )
+}
diff --git a/web/app/(landing-page)/(components)/landing-page-header.tsx b/web/app/(landing-page)/(components)/landing-page-header.tsx
index 46d0a0d..0c2fc43 100644
--- a/web/app/(landing-page)/(components)/landing-page-header.tsx
+++ b/web/app/(landing-page)/(components)/landing-page-header.tsx
@@ -29,9 +29,9 @@ export default function LandingPageHeader() {
*/}
- Github
+ Contribute
Support The Project
- Maintaining an open-source project requires time and dedication. By
- becoming a patron or donating cryptocurrency, your contribution will
- directly support the development, including implementation of new
- features, enhance performance, and ensure the highest level of
- security and reliability.
+ Maintaining an open-source project requires time and dedication.
+ Your contribution will directly support the development, including
+ implementation of new features, enhance performance, and ensure the
+ highest level of security and reliability.
-
+
-
+
Become a Patron
- setCryptoOpen(true)}>
+
+
+ Star on GitHub
+
+
+
+
+ One-time Donation
+
+
+ setCryptoOpen(true)}
+ className='sm:w-auto w-full'
+ >
Donate Crypto
diff --git a/web/components/shared/app-header.tsx b/web/components/shared/app-header.tsx
index 781aa73..ffa1823 100644
--- a/web/components/shared/app-header.tsx
+++ b/web/components/shared/app-header.tsx
@@ -108,6 +108,13 @@ export default function AppHeader() {
Dashboard
+
+
+ Contribute
+
+
+
+ Contribute
+
+
+
{isAuthenticated ? (
) : (
diff --git a/web/components/shared/contribute-modal.tsx b/web/components/shared/contribute-modal.tsx
new file mode 100644
index 0000000..bb1640f
--- /dev/null
+++ b/web/components/shared/contribute-modal.tsx
@@ -0,0 +1,141 @@
+'use client'
+
+import { useState, useEffect } from 'react'
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogHeader,
+ DialogTitle,
+} from '@/components/ui/dialog'
+import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
+import { Button } from '@/components/ui/button'
+import {
+ CircleDollarSign,
+ Github,
+ Heart,
+ MessageSquare,
+ Star,
+} from 'lucide-react'
+import Link from 'next/link'
+import { ExternalLinks } from '@/config/external-links'
+
+// Add constants for localStorage and timing
+const STORAGE_KEYS = {
+ LAST_SHOWN: 'contribute_modal_last_shown',
+ HAS_CONTRIBUTED: 'contribute_modal_has_contributed',
+}
+
+const SHOW_INTERVAL = 1 * 24 * 60 * 60 * 1000 // 1 days in milliseconds
+const RANDOM_CHANCE = 0.2 // 20% chance to show when eligible
+
+export function ContributeModal() {
+ const [isOpen, setIsOpen] = useState(false)
+
+ useEffect(() => {
+ const checkAndShowModal = () => {
+ const hasContributed =
+ localStorage.getItem(STORAGE_KEYS.HAS_CONTRIBUTED) === 'true'
+ if (hasContributed) return
+
+ const lastShown = localStorage.getItem(STORAGE_KEYS.LAST_SHOWN)
+ const now = Date.now()
+
+ if (!lastShown || now - parseInt(lastShown) >= SHOW_INTERVAL) {
+ if (Math.random() < RANDOM_CHANCE) {
+ setIsOpen(true)
+ localStorage.setItem(STORAGE_KEYS.LAST_SHOWN, now.toString())
+ }
+ }
+ }
+
+ checkAndShowModal()
+
+ document.addEventListener('visibilitychange', () => {
+ if (document.visibilityState === 'visible') {
+ checkAndShowModal()
+ }
+ })
+ }, [])
+
+ const handleContributed = () => {
+ localStorage.setItem(STORAGE_KEYS.HAS_CONTRIBUTED, 'true')
+ setIsOpen(false)
+ }
+
+ return (
+
+
+
+ Support textbee.dev
+
+ Your contribution helps keep this project alive and growing.
+
+
+
+
+
+
+
+
+ Financial Support
+
+
+
+
+
+
+
+ Monthly Support on Patreon
+
+
+
+
+
+ One-time Donation via Polar.sh
+
+
+
+
+
+
+
+
+
+ Code Contributions
+
+
+
+
+
+
+
+ Star on GitHub
+
+
+
+
+
+ Report Issue
+
+
+
+
+
+
+
+
+ I've already donated
+
+ setIsOpen(false)}>
+ Remind me later
+
+
+
+
+
+ )
+}
diff --git a/web/components/shared/join-community-modal.tsx b/web/components/shared/join-community-modal.tsx
new file mode 100644
index 0000000..3125760
--- /dev/null
+++ b/web/components/shared/join-community-modal.tsx
@@ -0,0 +1,96 @@
+'use client'
+
+import { useState, useEffect } from 'react'
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+} from '@/components/ui/dialog'
+import { Button } from '@/components/ui/button'
+import { ExternalLinks } from '@/config/external-links'
+
+// Constants for localStorage keys and timing
+const STORAGE_KEYS = {
+ LAST_SHOWN: 'discord_modal_last_shown',
+ HAS_JOINED: 'discord_modal_has_joined',
+}
+
+const SHOW_INTERVAL = 1 * 24 * 60 * 60 * 1000 // 1 days in milliseconds
+const RANDOM_CHANCE = 0.2 // 20% chance to show when eligible
+
+export const JoinCommunityModal = () => {
+ const [isOpen, setIsOpen] = useState(false)
+
+ useEffect(() => {
+ const checkAndShowModal = () => {
+ const hasJoined = localStorage.getItem(STORAGE_KEYS.HAS_JOINED) === 'true'
+ if (hasJoined) return
+
+ const lastShown = localStorage.getItem(STORAGE_KEYS.LAST_SHOWN)
+ const now = Date.now()
+
+ if (!lastShown || now - parseInt(lastShown) >= SHOW_INTERVAL) {
+ if (Math.random() < RANDOM_CHANCE) {
+ setIsOpen(true)
+ localStorage.setItem(STORAGE_KEYS.LAST_SHOWN, now.toString())
+ }
+ }
+ }
+
+ // Check when component mounts
+ checkAndShowModal()
+
+ // Also check when tab becomes visible
+ document.addEventListener('visibilitychange', () => {
+ if (document.visibilityState === 'visible') {
+ checkAndShowModal()
+ }
+ })
+ }, [])
+
+ const handleJoined = () => {
+ localStorage.setItem(STORAGE_KEYS.HAS_JOINED, 'true')
+ setIsOpen(false)
+ }
+
+ const handleRemindLater = () => {
+ setIsOpen(false)
+ }
+
+ return (
+
+
+
+ Join Our Discord Community!
+
+
+
+
+ Join our Discord community to connect with other users, get help,
+ and stay updated with the latest announcements!
+
+
+
+
+
+ Remind Me Later
+
+
+ I've Already Joined
+
+ {
+ window.open(ExternalLinks.discord, '_blank')
+ handleJoined()
+ }}
+ className='gap-2'
+ >
+ Join Discord
+
+
+
+
+ )
+}
diff --git a/web/config/external-links.ts b/web/config/external-links.ts
index 6f5ad7d..a901530 100644
--- a/web/config/external-links.ts
+++ b/web/config/external-links.ts
@@ -2,4 +2,5 @@ export const ExternalLinks = {
patreon: 'https://patreon.com/vernu',
github: 'https://github.com/vernu/textbee',
discord: 'https://discord.gg/d7vyfBpWbQ',
+ polar: 'https://donate.textbee.dev',
}
diff --git a/web/config/routes.ts b/web/config/routes.ts
index cf182fd..b55c12b 100644
--- a/web/config/routes.ts
+++ b/web/config/routes.ts
@@ -1,5 +1,6 @@
export const Routes = {
landingPage: '/',
+ contribute: '/contribute',
login: '/login',
register: '/register',
logout: '/logout',