From 9409d162cedc4c5b855cb034f53cd21ad2c68504 Mon Sep 17 00:00:00 2001 From: isra el Date: Mon, 6 Jan 2025 06:15:13 +0300 Subject: [PATCH] chore(web): add contribute page --- web/app/(app)/contribute/page.tsx | 290 ++++++++++++++++++ .../(components)/community-links.tsx | 53 ++-- web/app/(app)/dashboard/layout.tsx | 16 +- .../(components)/landing-page-header.tsx | 4 +- .../(components)/support-project-section.tsx | 49 ++- web/components/shared/app-header.tsx | 16 + web/components/shared/contribute-modal.tsx | 141 +++++++++ .../shared/join-community-modal.tsx | 96 ++++++ web/config/external-links.ts | 1 + web/config/routes.ts | 1 + 10 files changed, 628 insertions(+), 39 deletions(-) create mode 100644 web/app/(app)/contribute/page.tsx create mode 100644 web/components/shared/contribute-modal.tsx create mode 100644 web/components/shared/join-community-modal.tsx 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 + + + + + + +
+
+ + + One-time Support + + Make a one-time contribution + + + + + + +
+ + + + Crypto Donations + + Support us with cryptocurrency + + + + + + + + + + + Cryptocurrency Donation Addresses + + +
+ {cryptoWallets.map((wallet, index) => ( +
+
+ + {wallet.name.includes('Bitcoin') ? ( + + ) : wallet.name.includes('Ethereum') ? ( + + ) : ( + + )}{' '} + {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 + + + + + + + + + + Report Issues + + Help us improve by reporting bugs and suggesting features + + + + + + + + + + Security Reports + + Report security vulnerabilities privately to{' '} + + security@textbee.dev + + + + + + + +
+
+
+ + + + + + Join the Community + + + Connect with other contributors and users + + + + + + +
+
+ ) +} 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,7 +6,41 @@ import { ExternalLinks } from '@/config/external-links' export default function CommunityLinks() { return ( -
+
+ + + One-time Donation + + +

+ Support us with a one-time donation of your desired amount. +

+ + + +
+
+ + + + Support on Patreon + + +

+ Support the development by becoming a patron. +

+ + + +
+
+ GitHub @@ -24,23 +58,6 @@ export default function CommunityLinks() { - - - Support Us - - -

- Support the development by becoming a patron. -

- - - -
-
- 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.

-
+
- - + + + + +
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 + + + {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 + + + +
+ + +
+
+
+ + + + + Code Contributions + + + +
+ + +
+
+
+ +
+ + +
+
+
+
+ ) +} 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! +

+
+ +
+ + + +
+
+
+ ) +} 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',