From 70aa1ad46ee53b4b6ac99cf5cc7f152822dbdc14 Mon Sep 17 00:00:00 2001 From: Ryan Salazar Date: Thu, 7 May 2026 16:39:17 +0000 Subject: [PATCH] initial commit: rocket.new export of broadcastbeat --- .eslintrc.json | 57 + .gitignore | 42 + .prettierignore | 5 + .prettierrc | 9 + README.md | 91 ++ config/ai-nodes.json | 26 + image-hosts.config.js | 34 + next-env.d.ts | 5 + next.config.mjs | 133 ++ package.json | 96 ++ postcss.config.js | 6 + public/assets/images/no_image.png | Bin 0 -> 20735 bytes public/favicon.ico | Bin 0 -> 4158 bytes src/app/about/page.tsx | 123 ++ src/app/account/page.tsx | 842 ++++++++++ src/app/admin/accounting/clients/page.tsx | 210 +++ src/app/admin/accounting/commissions/page.tsx | 133 ++ src/app/admin/accounting/documents/page.tsx | 149 ++ src/app/admin/accounting/expenses/page.tsx | 198 +++ src/app/admin/accounting/invoices/page.tsx | 202 +++ src/app/admin/accounting/orders/page.tsx | 276 ++++ src/app/admin/accounting/page.tsx | 157 ++ .../admin/accounting/reconciliation/page.tsx | 116 ++ src/app/admin/accounting/reports/page.tsx | 122 ++ src/app/admin/accounting/staff/page.tsx | 210 +++ src/app/admin/adops/ios/page.tsx | 249 +++ src/app/admin/adops/notifications/page.tsx | 69 + src/app/admin/adops/page.tsx | 164 ++ src/app/admin/adops/products/page.tsx | 150 ++ src/app/admin/analytics/page.tsx | 584 +++++++ src/app/admin/articles/page.tsx | 1106 ++++++++++++++ src/app/admin/audit-log/page.tsx | 619 ++++++++ src/app/admin/banned-terms/page.tsx | 389 +++++ src/app/admin/company-coverage/page.tsx | 483 ++++++ src/app/admin/company-tracker/page.tsx | 627 ++++++++ src/app/admin/content-status/page.tsx | 514 +++++++ src/app/admin/email-analytics/page.tsx | 617 ++++++++ src/app/admin/email/domains/page.tsx | 226 +++ src/app/admin/email/page.tsx | 124 ++ src/app/admin/email/send-log/page.tsx | 89 ++ src/app/admin/email/suppressions/page.tsx | 229 +++ src/app/admin/forum-dashboard/page.tsx | 565 +++++++ src/app/admin/forum-maintenance/page.tsx | 21 + src/app/admin/forum-moderation/page.tsx | 580 +++++++ src/app/admin/forum-seed/page.tsx | 298 ++++ src/app/admin/import/page.tsx | 462 ++++++ src/app/admin/newsletter/analytics/page.tsx | 473 ++++++ .../admin/newsletter/campaign-editor/page.tsx | 730 +++++++++ src/app/admin/newsletter/page.tsx | 868 +++++++++++ src/app/admin/newsletter/templates/page.tsx | 702 +++++++++ src/app/admin/notifications/page.tsx | 624 ++++++++ src/app/admin/page.tsx | 619 ++++++++ src/app/admin/rmp/page.tsx | 104 ++ src/app/admin/roles/page.tsx | 732 +++++++++ src/app/admin/suspensions/page.tsx | 764 ++++++++++ src/app/admin/users/page.tsx | 721 +++++++++ src/app/admin/wp-users/page.tsx | 300 ++++ src/app/advertise/layout.tsx | 17 + src/app/advertise/page.tsx | 259 ++++ src/app/api/accounting/clients/route.ts | 43 + .../commissions/[id]/mark-paid/route.ts | 19 + src/app/api/accounting/commissions/route.ts | 26 + .../accounting/commissions/summary/route.ts | 39 + src/app/api/accounting/dashboard/route.ts | 88 ++ src/app/api/accounting/documents/route.ts | 42 + src/app/api/accounting/expenses/route.ts | 44 + .../invoices/[id]/mark-paid/route.ts | 29 + src/app/api/accounting/invoices/route.ts | 51 + .../api/accounting/notify-payment/route.ts | 38 + src/app/api/accounting/orders/route.ts | 59 + src/app/api/accounting/products/[id]/route.ts | 20 + src/app/api/accounting/products/route.ts | 42 + .../api/accounting/reconciliation/route.ts | 92 ++ src/app/api/accounting/reports/route.ts | 49 + src/app/api/accounting/staff/route.ts | 66 + .../api/accounting/stripe-webhook/route.ts | 51 + src/app/api/admin/articles/route.ts | 200 +++ src/app/api/admin/banned-term-alert/route.ts | 39 + src/app/api/admin/banned-terms-scan/route.ts | 45 + src/app/api/admin/banned-terms/route.ts | 105 ++ .../admin/company-coverage-reminder/route.ts | 48 + src/app/api/admin/company-coverage/route.ts | 347 +++++ .../api/admin/display-name-overrides/route.ts | 75 + src/app/api/admin/forum-moderation/route.ts | 153 ++ src/app/api/admin/forum-stats/route.ts | 186 +++ src/app/api/admin/roles/route.ts | 160 ++ src/app/api/admin/show-calendar/route.ts | 616 ++++++++ src/app/api/admin/suspensions/route.ts | 310 ++++ src/app/api/admin/users/route.ts | 164 ++ src/app/api/adops/campaigns/route.ts | 100 ++ src/app/api/adops/cron/end-campaigns/route.ts | 92 ++ .../api/adops/cron/rmp-deactivate/route.ts | 56 + .../adops/cron/rmp-renewal-warnings/route.ts | 54 + src/app/api/adops/email-campaigns/route.ts | 96 ++ src/app/api/adops/onboard/route.ts | 34 + src/app/api/adops/poll/route.ts | 76 + src/app/api/adops/preview/route.ts | 91 ++ src/app/api/adops/rmp/email-schedule/route.ts | 20 + src/app/api/adops/rmp/flights/route.ts | 29 + .../api/adops/rmp/ios/[id]/status/route.ts | 26 + src/app/api/adops/rmp/ios/route.ts | 72 + src/app/api/adops/rmp/notifications/route.ts | 20 + src/app/api/adops/rmp/stats/route.ts | 31 + src/app/api/adops/stats/route.ts | 64 + src/app/api/ai/article-suggestions/route.ts | 80 + src/app/api/ai/chat-completion/route.ts | 100 ++ src/app/api/ai/console-settings/route.ts | 208 +++ src/app/api/ai/console/route.ts | 238 +++ src/app/api/ai/fetch-url/route.ts | 92 ++ src/app/api/ai/health-log/route.ts | 95 ++ src/app/api/ai/node-health/route.ts | 53 + src/app/api/ai/ollama-health/route.ts | 5 + src/app/api/auth/wp-login/route.ts | 120 ++ src/app/api/billing/clients/route.ts | 64 + src/app/api/billing/invoices/route.ts | 112 ++ src/app/api/billing/payments/route.ts | 31 + src/app/api/billing/settings/route.ts | 59 + src/app/api/billing/stats/route.ts | 34 + src/app/api/billing/sync/route.ts | 150 ++ src/app/api/content/audit/route.ts | 126 ++ src/app/api/content/generate/route.ts | 54 + src/app/api/content/pipeline-status/route.ts | 88 ++ src/app/api/content/press-releases/route.ts | 132 ++ src/app/api/content/seo-batch/route.ts | 84 + src/app/api/contributor/posts/route.ts | 148 ++ src/app/api/contributor/scope-check/route.ts | 40 + src/app/api/digest/preferences/route.ts | 32 + .../email/domains/[domain_id]/verify/route.ts | 80 + .../api/email/domains/reset-counts/route.ts | 18 + src/app/api/email/domains/verify-all/route.ts | 44 + src/app/api/email/inbound-reply/route.ts | 75 + .../email/rmp/domains/[id]/bypass/route.ts | 35 + src/app/api/email/rmp/domains/[id]/route.ts | 20 + src/app/api/email/rmp/domains/route.ts | 41 + src/app/api/email/rmp/keywords/[id]/route.ts | 20 + src/app/api/email/rmp/keywords/route.ts | 31 + src/app/api/email/rmp/send-log/route.ts | 27 + src/app/api/email/rmp/stats/route.ts | 32 + .../api/email/rmp/suppressions/[id]/route.ts | 19 + src/app/api/email/rmp/suppressions/route.ts | 44 + .../api/email/rmp/suppressions/stats/route.ts | 25 + src/app/api/forum/ai-assist/route.ts | 66 + src/app/api/forum/auto-moderate/route.ts | 104 ++ src/app/api/forum/categories/route.ts | 17 + src/app/api/forum/following-feed/route.ts | 64 + src/app/api/forum/follows/route.ts | 143 ++ src/app/api/forum/replies/route.ts | 95 ++ src/app/api/forum/threads/[id]/route.ts | 37 + src/app/api/forum/threads/route.ts | 156 ++ src/app/api/forum/user-profile/route.ts | 89 ++ src/app/api/forum/votes/route.ts | 136 ++ src/app/api/marketplace/admin/notify/route.ts | 62 + src/app/api/marketplace/send-email/route.ts | 34 + .../api/marketplace/typesense-index/route.ts | 153 ++ src/app/api/news/share-email/route.ts | 88 ++ src/app/api/newsletter/archive/route.ts | 76 + .../api/newsletter/notify-article/route.ts | 75 + src/app/api/newsletter/subscribe/route.ts | 76 + src/app/api/newsletter/subscribers/route.ts | 187 +++ src/app/api/newsletter/templates/route.ts | 96 ++ src/app/api/notifications/route.ts | 85 ++ src/app/api/translations/route.ts | 316 ++++ src/app/api/webhooks/stripe/jtr/route.ts | 100 ++ src/app/api/webhooks/stripe/rmp/route.ts | 106 ++ src/app/api/wp-import/route.ts | 294 ++++ .../articles/[slug]/ArticleDetailClient.tsx | 409 +++++ .../[slug]/components/CommentsSection.tsx | 249 +++ src/app/articles/[slug]/page.tsx | 105 ++ src/app/auth/callback/route.ts | 19 + src/app/authors/[slug]/page.tsx | 482 ++++++ src/app/contributor/page.tsx | 574 +++++++ src/app/dashboard/adops/page.tsx | 709 +++++++++ src/app/dashboard/ai-console/health/page.tsx | 190 +++ src/app/dashboard/ai-console/page.tsx | 1355 +++++++++++++++++ .../dashboard/ai-console/settings/page.tsx | 444 ++++++ .../billing/components/BillingSettingsTab.tsx | 415 +++++ .../billing/components/ClientsTab.tsx | 195 +++ .../billing/components/InvoicesTab.tsx | 331 ++++ .../billing/components/OverviewTab.tsx | 168 ++ .../billing/components/PaymentsTab.tsx | 121 ++ .../billing/components/ReportsTab.tsx | 195 +++ src/app/dashboard/billing/page.tsx | 158 ++ src/app/dashboard/editorial/page.tsx | 543 +++++++ src/app/dashboard/show-calendar/page.tsx | 878 +++++++++++ src/app/forum/ForumIndexPage.tsx | 15 + src/app/forum/ForumSEOWrapper.tsx | 2 + src/app/forum/[slug]/page.tsx | 471 ++++++ src/app/forum/layout.tsx | 50 + src/app/forum/page.tsx | 575 +++++++ src/app/forum/thread/[id]/page.tsx | 492 ++++++ src/app/gear/page.tsx | 86 ++ src/app/home-page/components/ArticleFeed.tsx | 959 ++++++++++++ .../home-page/components/FeaturedBento.tsx | 270 ++++ .../home-page/components/LeaderboardAd.tsx | 22 + src/app/home-page/components/NewsTicker.tsx | 48 + .../home-page/components/NewsletterSignup.tsx | 179 +++ .../components/SpotlightCarousel.tsx | 234 +++ src/app/home-page/page.tsx | 198 +++ src/app/layout.tsx | 135 ++ src/app/login/page.tsx | 176 +++ src/app/marketplace/account/billing/page.tsx | 157 ++ src/app/marketplace/account/page.tsx | 561 +++++++ src/app/marketplace/admin/queue/page.tsx | 419 +++++ src/app/marketplace/gigs/[id]/page.tsx | 107 ++ src/app/marketplace/gigs/page.tsx | 112 ++ src/app/marketplace/jobs/[id]/page.tsx | 122 ++ src/app/marketplace/jobs/page.tsx | 112 ++ src/app/marketplace/join/page.tsx | 673 ++++++++ src/app/marketplace/page.tsx | 260 ++++ src/app/marketplace/profile/[slug]/page.tsx | 193 +++ src/app/marketplace/search/page.tsx | 533 +++++++ .../news/[slug]/NewsArticleDetailClient.tsx | 782 ++++++++++ src/app/news/[slug]/page.tsx | 106 ++ src/app/news/page.tsx | 295 ++++ src/app/newsletter/archive/page.tsx | 596 ++++++++ src/app/not-found.tsx | 54 + src/app/privacy/page.tsx | 139 ++ src/app/profile/[userId]/page.tsx | 809 ++++++++++ src/app/reading-list/page.tsx | 316 ++++ src/app/register/page.tsx | 236 +++ src/app/robots.ts | 22 + src/app/show-coverage/page.tsx | 149 ++ src/app/sitemap.ts | 41 + src/app/technology/page.tsx | 115 ++ src/app/terms/page.tsx | 56 + src/components/AISuggestedArticles.tsx | 297 ++++ src/components/CookieConsent.tsx | 91 ++ src/components/Footer.tsx | 163 ++ src/components/GoogleAnalytics.tsx | 45 + src/components/Header.tsx | 514 +++++++ src/components/LanguageSwitcher.tsx | 168 ++ src/components/NotificationCenter.tsx | 388 +++++ src/components/RichTextEditor.tsx | 198 +++ src/components/ScrollRevealSection.tsx | 48 + src/components/admin/ForumMaintenanceTool.jsx | 10 + src/components/admin/RmpAdminNav.tsx | 120 ++ src/components/adops/RoleGate.tsx | 125 ++ src/components/ui/AppIcon.tsx | 55 + src/components/ui/AppImage.tsx | 123 ++ src/components/ui/AppLogo.tsx | 50 + src/components/ui/Icons.tsx | 281 ++++ src/contexts/AuthContext.tsx | 273 ++++ src/hooks/useScrollReveal.ts | 37 + src/lib/accounting/rmpService.ts | 193 +++ src/lib/adops/adOpsConfig.ts | 121 ++ src/lib/adops/adOpsEmailSender.ts | 85 ++ src/lib/adops/adOpsInvoiceService.ts | 165 ++ src/lib/adops/emailTemplates.ts | 306 ++++ src/lib/adops/imapMonitor.ts | 493 ++++++ src/lib/adops/screenshotService.ts | 106 ++ src/lib/ai/aiClient.ts | 24 + src/lib/ai/aiNodeRouter.ts | 199 +++ src/lib/ai/chatCompletion.ts | 79 + src/lib/ai/hybridRouter.ts | 366 +++++ src/lib/articles/sampleArticles.ts | 939 ++++++++++++ src/lib/auth/permissions.ts | 260 ++++ src/lib/auth/phpass.ts | 110 ++ src/lib/billing/billingService.ts | 317 ++++ src/lib/billing/moonInvoiceClient.ts | 148 ++ src/lib/content/contentPipeline.ts | 298 ++++ src/lib/hooks/useChat.ts | 52 + src/lib/marketplace/emailService.ts | 128 ++ src/lib/marketplace/typesenseClient.ts | 130 ++ src/lib/supabase/client.tsx | 30 + src/lib/supabase/server.tsx | 28 + src/styles/index.css | 1 + src/styles/tailwind.css | 810 ++++++++++ .../send-article-notification/index.ts | 164 ++ .../send-contributor-invite/index.ts | 119 ++ .../send-newsletter-confirmation/index.ts | 124 ++ .../functions/send-weekly-digest/index.ts | 381 +++++ ...60312053000_reading_list_and_analytics.sql | 84 + ...60312060000_user_profiles_and_comments.sql | 127 ++ .../20260320230000_wp_imported_posts.sql | 95 ++ .../20260321065000_post_images_storage.sql | 50 + .../20260321070000_native_articles.sql | 53 + ...260321080000_article_status_scheduling.sql | 51 + ...60321090000_user_roles_and_invitations.sql | 93 ++ .../migrations/20260321100000_audit_logs.sql | 97 ++ .../20260321110000_newsletter_subscribers.sql | 33 + .../20260321130000_newsletter_templates.sql | 91 ++ .../20260321170000_community_forum.sql | 454 ++++++ .../20260321190000_forum_votes_reputation.sql | 185 +++ .../20260321200000_notifications.sql | 144 ++ ...260321210000_weekly_digest_preferences.sql | 8 + .../20260321220000_forum_moderation.sql | 51 + .../migrations/20260322120000_user_badges.sql | 156 ++ .../20260322160000_user_follows.sql | 40 + .../20260322190000_role_permissions.sql | 82 + .../20260322200000_user_suspensions.sql | 129 ++ .../20260322210000_wp_user_migration.sql | 269 ++++ .../20260322220000_company_coverage.sql | 81 + .../20260322230000_multisite_system.sql | 403 +++++ .../20260322240000_sections_15_to_19.sql | 599 ++++++++ ...0260322250000_ai_console_hybrid_router.sql | 164 ++ .../20260322260000_unified_billing_system.sql | 343 +++++ .../20260322270000_adops_system.sql | 403 +++++ .../20260401000000_marketplace_phase1.sql | 360 +++++ .../20260401120000_rmp_master_system.sql | 570 +++++++ ...0260402050000_user_reading_preferences.sql | 72 + tailwind.config.js | 64 + tsconfig.json | 42 + 302 files changed, 60710 insertions(+) create mode 100644 .eslintrc.json create mode 100644 .gitignore create mode 100644 .prettierignore create mode 100644 .prettierrc create mode 100644 README.md create mode 100644 config/ai-nodes.json create mode 100644 image-hosts.config.js create mode 100644 next-env.d.ts create mode 100644 next.config.mjs create mode 100644 package.json create mode 100644 postcss.config.js create mode 100644 public/assets/images/no_image.png create mode 100644 public/favicon.ico create mode 100644 src/app/about/page.tsx create mode 100644 src/app/account/page.tsx create mode 100644 src/app/admin/accounting/clients/page.tsx create mode 100644 src/app/admin/accounting/commissions/page.tsx create mode 100644 src/app/admin/accounting/documents/page.tsx create mode 100644 src/app/admin/accounting/expenses/page.tsx create mode 100644 src/app/admin/accounting/invoices/page.tsx create mode 100644 src/app/admin/accounting/orders/page.tsx create mode 100644 src/app/admin/accounting/page.tsx create mode 100644 src/app/admin/accounting/reconciliation/page.tsx create mode 100644 src/app/admin/accounting/reports/page.tsx create mode 100644 src/app/admin/accounting/staff/page.tsx create mode 100644 src/app/admin/adops/ios/page.tsx create mode 100644 src/app/admin/adops/notifications/page.tsx create mode 100644 src/app/admin/adops/page.tsx create mode 100644 src/app/admin/adops/products/page.tsx create mode 100644 src/app/admin/analytics/page.tsx create mode 100644 src/app/admin/articles/page.tsx create mode 100644 src/app/admin/audit-log/page.tsx create mode 100644 src/app/admin/banned-terms/page.tsx create mode 100644 src/app/admin/company-coverage/page.tsx create mode 100644 src/app/admin/company-tracker/page.tsx create mode 100644 src/app/admin/content-status/page.tsx create mode 100644 src/app/admin/email-analytics/page.tsx create mode 100644 src/app/admin/email/domains/page.tsx create mode 100644 src/app/admin/email/page.tsx create mode 100644 src/app/admin/email/send-log/page.tsx create mode 100644 src/app/admin/email/suppressions/page.tsx create mode 100644 src/app/admin/forum-dashboard/page.tsx create mode 100644 src/app/admin/forum-maintenance/page.tsx create mode 100644 src/app/admin/forum-moderation/page.tsx create mode 100644 src/app/admin/forum-seed/page.tsx create mode 100644 src/app/admin/import/page.tsx create mode 100644 src/app/admin/newsletter/analytics/page.tsx create mode 100644 src/app/admin/newsletter/campaign-editor/page.tsx create mode 100644 src/app/admin/newsletter/page.tsx create mode 100644 src/app/admin/newsletter/templates/page.tsx create mode 100644 src/app/admin/notifications/page.tsx create mode 100644 src/app/admin/page.tsx create mode 100644 src/app/admin/rmp/page.tsx create mode 100644 src/app/admin/roles/page.tsx create mode 100644 src/app/admin/suspensions/page.tsx create mode 100644 src/app/admin/users/page.tsx create mode 100644 src/app/admin/wp-users/page.tsx create mode 100644 src/app/advertise/layout.tsx create mode 100644 src/app/advertise/page.tsx create mode 100644 src/app/api/accounting/clients/route.ts create mode 100644 src/app/api/accounting/commissions/[id]/mark-paid/route.ts create mode 100644 src/app/api/accounting/commissions/route.ts create mode 100644 src/app/api/accounting/commissions/summary/route.ts create mode 100644 src/app/api/accounting/dashboard/route.ts create mode 100644 src/app/api/accounting/documents/route.ts create mode 100644 src/app/api/accounting/expenses/route.ts create mode 100644 src/app/api/accounting/invoices/[id]/mark-paid/route.ts create mode 100644 src/app/api/accounting/invoices/route.ts create mode 100644 src/app/api/accounting/notify-payment/route.ts create mode 100644 src/app/api/accounting/orders/route.ts create mode 100644 src/app/api/accounting/products/[id]/route.ts create mode 100644 src/app/api/accounting/products/route.ts create mode 100644 src/app/api/accounting/reconciliation/route.ts create mode 100644 src/app/api/accounting/reports/route.ts create mode 100644 src/app/api/accounting/staff/route.ts create mode 100644 src/app/api/accounting/stripe-webhook/route.ts create mode 100644 src/app/api/admin/articles/route.ts create mode 100644 src/app/api/admin/banned-term-alert/route.ts create mode 100644 src/app/api/admin/banned-terms-scan/route.ts create mode 100644 src/app/api/admin/banned-terms/route.ts create mode 100644 src/app/api/admin/company-coverage-reminder/route.ts create mode 100644 src/app/api/admin/company-coverage/route.ts create mode 100644 src/app/api/admin/display-name-overrides/route.ts create mode 100644 src/app/api/admin/forum-moderation/route.ts create mode 100644 src/app/api/admin/forum-stats/route.ts create mode 100644 src/app/api/admin/roles/route.ts create mode 100644 src/app/api/admin/show-calendar/route.ts create mode 100644 src/app/api/admin/suspensions/route.ts create mode 100644 src/app/api/admin/users/route.ts create mode 100644 src/app/api/adops/campaigns/route.ts create mode 100644 src/app/api/adops/cron/end-campaigns/route.ts create mode 100644 src/app/api/adops/cron/rmp-deactivate/route.ts create mode 100644 src/app/api/adops/cron/rmp-renewal-warnings/route.ts create mode 100644 src/app/api/adops/email-campaigns/route.ts create mode 100644 src/app/api/adops/onboard/route.ts create mode 100644 src/app/api/adops/poll/route.ts create mode 100644 src/app/api/adops/preview/route.ts create mode 100644 src/app/api/adops/rmp/email-schedule/route.ts create mode 100644 src/app/api/adops/rmp/flights/route.ts create mode 100644 src/app/api/adops/rmp/ios/[id]/status/route.ts create mode 100644 src/app/api/adops/rmp/ios/route.ts create mode 100644 src/app/api/adops/rmp/notifications/route.ts create mode 100644 src/app/api/adops/rmp/stats/route.ts create mode 100644 src/app/api/adops/stats/route.ts create mode 100644 src/app/api/ai/article-suggestions/route.ts create mode 100644 src/app/api/ai/chat-completion/route.ts create mode 100644 src/app/api/ai/console-settings/route.ts create mode 100644 src/app/api/ai/console/route.ts create mode 100644 src/app/api/ai/fetch-url/route.ts create mode 100644 src/app/api/ai/health-log/route.ts create mode 100644 src/app/api/ai/node-health/route.ts create mode 100644 src/app/api/ai/ollama-health/route.ts create mode 100644 src/app/api/auth/wp-login/route.ts create mode 100644 src/app/api/billing/clients/route.ts create mode 100644 src/app/api/billing/invoices/route.ts create mode 100644 src/app/api/billing/payments/route.ts create mode 100644 src/app/api/billing/settings/route.ts create mode 100644 src/app/api/billing/stats/route.ts create mode 100644 src/app/api/billing/sync/route.ts create mode 100644 src/app/api/content/audit/route.ts create mode 100644 src/app/api/content/generate/route.ts create mode 100644 src/app/api/content/pipeline-status/route.ts create mode 100644 src/app/api/content/press-releases/route.ts create mode 100644 src/app/api/content/seo-batch/route.ts create mode 100644 src/app/api/contributor/posts/route.ts create mode 100644 src/app/api/contributor/scope-check/route.ts create mode 100644 src/app/api/digest/preferences/route.ts create mode 100644 src/app/api/email/domains/[domain_id]/verify/route.ts create mode 100644 src/app/api/email/domains/reset-counts/route.ts create mode 100644 src/app/api/email/domains/verify-all/route.ts create mode 100644 src/app/api/email/inbound-reply/route.ts create mode 100644 src/app/api/email/rmp/domains/[id]/bypass/route.ts create mode 100644 src/app/api/email/rmp/domains/[id]/route.ts create mode 100644 src/app/api/email/rmp/domains/route.ts create mode 100644 src/app/api/email/rmp/keywords/[id]/route.ts create mode 100644 src/app/api/email/rmp/keywords/route.ts create mode 100644 src/app/api/email/rmp/send-log/route.ts create mode 100644 src/app/api/email/rmp/stats/route.ts create mode 100644 src/app/api/email/rmp/suppressions/[id]/route.ts create mode 100644 src/app/api/email/rmp/suppressions/route.ts create mode 100644 src/app/api/email/rmp/suppressions/stats/route.ts create mode 100644 src/app/api/forum/ai-assist/route.ts create mode 100644 src/app/api/forum/auto-moderate/route.ts create mode 100644 src/app/api/forum/categories/route.ts create mode 100644 src/app/api/forum/following-feed/route.ts create mode 100644 src/app/api/forum/follows/route.ts create mode 100644 src/app/api/forum/replies/route.ts create mode 100644 src/app/api/forum/threads/[id]/route.ts create mode 100644 src/app/api/forum/threads/route.ts create mode 100644 src/app/api/forum/user-profile/route.ts create mode 100644 src/app/api/forum/votes/route.ts create mode 100644 src/app/api/marketplace/admin/notify/route.ts create mode 100644 src/app/api/marketplace/send-email/route.ts create mode 100644 src/app/api/marketplace/typesense-index/route.ts create mode 100644 src/app/api/news/share-email/route.ts create mode 100644 src/app/api/newsletter/archive/route.ts create mode 100644 src/app/api/newsletter/notify-article/route.ts create mode 100644 src/app/api/newsletter/subscribe/route.ts create mode 100644 src/app/api/newsletter/subscribers/route.ts create mode 100644 src/app/api/newsletter/templates/route.ts create mode 100644 src/app/api/notifications/route.ts create mode 100644 src/app/api/translations/route.ts create mode 100644 src/app/api/webhooks/stripe/jtr/route.ts create mode 100644 src/app/api/webhooks/stripe/rmp/route.ts create mode 100644 src/app/api/wp-import/route.ts create mode 100644 src/app/articles/[slug]/ArticleDetailClient.tsx create mode 100644 src/app/articles/[slug]/components/CommentsSection.tsx create mode 100644 src/app/articles/[slug]/page.tsx create mode 100644 src/app/auth/callback/route.ts create mode 100644 src/app/authors/[slug]/page.tsx create mode 100644 src/app/contributor/page.tsx create mode 100644 src/app/dashboard/adops/page.tsx create mode 100644 src/app/dashboard/ai-console/health/page.tsx create mode 100644 src/app/dashboard/ai-console/page.tsx create mode 100644 src/app/dashboard/ai-console/settings/page.tsx create mode 100644 src/app/dashboard/billing/components/BillingSettingsTab.tsx create mode 100644 src/app/dashboard/billing/components/ClientsTab.tsx create mode 100644 src/app/dashboard/billing/components/InvoicesTab.tsx create mode 100644 src/app/dashboard/billing/components/OverviewTab.tsx create mode 100644 src/app/dashboard/billing/components/PaymentsTab.tsx create mode 100644 src/app/dashboard/billing/components/ReportsTab.tsx create mode 100644 src/app/dashboard/billing/page.tsx create mode 100644 src/app/dashboard/editorial/page.tsx create mode 100644 src/app/dashboard/show-calendar/page.tsx create mode 100644 src/app/forum/ForumIndexPage.tsx create mode 100644 src/app/forum/ForumSEOWrapper.tsx create mode 100644 src/app/forum/[slug]/page.tsx create mode 100644 src/app/forum/layout.tsx create mode 100644 src/app/forum/page.tsx create mode 100644 src/app/forum/thread/[id]/page.tsx create mode 100644 src/app/gear/page.tsx create mode 100644 src/app/home-page/components/ArticleFeed.tsx create mode 100644 src/app/home-page/components/FeaturedBento.tsx create mode 100644 src/app/home-page/components/LeaderboardAd.tsx create mode 100644 src/app/home-page/components/NewsTicker.tsx create mode 100644 src/app/home-page/components/NewsletterSignup.tsx create mode 100644 src/app/home-page/components/SpotlightCarousel.tsx create mode 100644 src/app/home-page/page.tsx create mode 100644 src/app/layout.tsx create mode 100644 src/app/login/page.tsx create mode 100644 src/app/marketplace/account/billing/page.tsx create mode 100644 src/app/marketplace/account/page.tsx create mode 100644 src/app/marketplace/admin/queue/page.tsx create mode 100644 src/app/marketplace/gigs/[id]/page.tsx create mode 100644 src/app/marketplace/gigs/page.tsx create mode 100644 src/app/marketplace/jobs/[id]/page.tsx create mode 100644 src/app/marketplace/jobs/page.tsx create mode 100644 src/app/marketplace/join/page.tsx create mode 100644 src/app/marketplace/page.tsx create mode 100644 src/app/marketplace/profile/[slug]/page.tsx create mode 100644 src/app/marketplace/search/page.tsx create mode 100644 src/app/news/[slug]/NewsArticleDetailClient.tsx create mode 100644 src/app/news/[slug]/page.tsx create mode 100644 src/app/news/page.tsx create mode 100644 src/app/newsletter/archive/page.tsx create mode 100644 src/app/not-found.tsx create mode 100644 src/app/privacy/page.tsx create mode 100644 src/app/profile/[userId]/page.tsx create mode 100644 src/app/reading-list/page.tsx create mode 100644 src/app/register/page.tsx create mode 100644 src/app/robots.ts create mode 100644 src/app/show-coverage/page.tsx create mode 100644 src/app/sitemap.ts create mode 100644 src/app/technology/page.tsx create mode 100644 src/app/terms/page.tsx create mode 100644 src/components/AISuggestedArticles.tsx create mode 100644 src/components/CookieConsent.tsx create mode 100644 src/components/Footer.tsx create mode 100644 src/components/GoogleAnalytics.tsx create mode 100644 src/components/Header.tsx create mode 100644 src/components/LanguageSwitcher.tsx create mode 100644 src/components/NotificationCenter.tsx create mode 100644 src/components/RichTextEditor.tsx create mode 100644 src/components/ScrollRevealSection.tsx create mode 100644 src/components/admin/ForumMaintenanceTool.jsx create mode 100644 src/components/admin/RmpAdminNav.tsx create mode 100644 src/components/adops/RoleGate.tsx create mode 100644 src/components/ui/AppIcon.tsx create mode 100644 src/components/ui/AppImage.tsx create mode 100644 src/components/ui/AppLogo.tsx create mode 100644 src/components/ui/Icons.tsx create mode 100644 src/contexts/AuthContext.tsx create mode 100644 src/hooks/useScrollReveal.ts create mode 100644 src/lib/accounting/rmpService.ts create mode 100644 src/lib/adops/adOpsConfig.ts create mode 100644 src/lib/adops/adOpsEmailSender.ts create mode 100644 src/lib/adops/adOpsInvoiceService.ts create mode 100644 src/lib/adops/emailTemplates.ts create mode 100644 src/lib/adops/imapMonitor.ts create mode 100644 src/lib/adops/screenshotService.ts create mode 100644 src/lib/ai/aiClient.ts create mode 100644 src/lib/ai/aiNodeRouter.ts create mode 100644 src/lib/ai/chatCompletion.ts create mode 100644 src/lib/ai/hybridRouter.ts create mode 100644 src/lib/articles/sampleArticles.ts create mode 100644 src/lib/auth/permissions.ts create mode 100644 src/lib/auth/phpass.ts create mode 100644 src/lib/billing/billingService.ts create mode 100644 src/lib/billing/moonInvoiceClient.ts create mode 100644 src/lib/content/contentPipeline.ts create mode 100644 src/lib/hooks/useChat.ts create mode 100644 src/lib/marketplace/emailService.ts create mode 100644 src/lib/marketplace/typesenseClient.ts create mode 100644 src/lib/supabase/client.tsx create mode 100644 src/lib/supabase/server.tsx create mode 100644 src/styles/index.css create mode 100644 src/styles/tailwind.css create mode 100644 supabase/functions/send-article-notification/index.ts create mode 100644 supabase/functions/send-contributor-invite/index.ts create mode 100644 supabase/functions/send-newsletter-confirmation/index.ts create mode 100644 supabase/functions/send-weekly-digest/index.ts create mode 100644 supabase/migrations/20260312053000_reading_list_and_analytics.sql create mode 100644 supabase/migrations/20260312060000_user_profiles_and_comments.sql create mode 100644 supabase/migrations/20260320230000_wp_imported_posts.sql create mode 100644 supabase/migrations/20260321065000_post_images_storage.sql create mode 100644 supabase/migrations/20260321070000_native_articles.sql create mode 100644 supabase/migrations/20260321080000_article_status_scheduling.sql create mode 100644 supabase/migrations/20260321090000_user_roles_and_invitations.sql create mode 100644 supabase/migrations/20260321100000_audit_logs.sql create mode 100644 supabase/migrations/20260321110000_newsletter_subscribers.sql create mode 100644 supabase/migrations/20260321130000_newsletter_templates.sql create mode 100644 supabase/migrations/20260321170000_community_forum.sql create mode 100644 supabase/migrations/20260321190000_forum_votes_reputation.sql create mode 100644 supabase/migrations/20260321200000_notifications.sql create mode 100644 supabase/migrations/20260321210000_weekly_digest_preferences.sql create mode 100644 supabase/migrations/20260321220000_forum_moderation.sql create mode 100644 supabase/migrations/20260322120000_user_badges.sql create mode 100644 supabase/migrations/20260322160000_user_follows.sql create mode 100644 supabase/migrations/20260322190000_role_permissions.sql create mode 100644 supabase/migrations/20260322200000_user_suspensions.sql create mode 100644 supabase/migrations/20260322210000_wp_user_migration.sql create mode 100644 supabase/migrations/20260322220000_company_coverage.sql create mode 100644 supabase/migrations/20260322230000_multisite_system.sql create mode 100644 supabase/migrations/20260322240000_sections_15_to_19.sql create mode 100644 supabase/migrations/20260322250000_ai_console_hybrid_router.sql create mode 100644 supabase/migrations/20260322260000_unified_billing_system.sql create mode 100644 supabase/migrations/20260322270000_adops_system.sql create mode 100644 supabase/migrations/20260401000000_marketplace_phase1.sql create mode 100644 supabase/migrations/20260401120000_rmp_master_system.sql create mode 100644 supabase/migrations/20260402050000_user_reading_preferences.sql create mode 100644 tailwind.config.js create mode 100644 tsconfig.json diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..385c288 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,57 @@ +{ + "root": true, + "extends": [ + "next/core-web-vitals", + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + "plugin:prettier/recommended" + ], + "parser": "@typescript-eslint/parser", + "plugins": [ + "@typescript-eslint", + "prettier" + ], + "rules": { + "prettier/prettier": [ + "error", + { + "endOfLine": "auto", + "singleQuote": true, + "semi": true, + "tabWidth": 2, + "printWidth": 100, + "trailingComma": "es5" + } + ], + "@typescript-eslint/no-unused-vars": [ + "warn", + { + "argsIgnorePattern": "^_", + "varsIgnorePattern": "^_", + "caughtErrorsIgnorePattern": "^_" + } + ], + "@typescript-eslint/no-explicit-any": "warn", + "no-console": [ + "warn", + { + "allow": [ + "warn", + "error", + "info" + ] + } + ] + }, + "ignorePatterns": [ + "node_modules/", + ".next/", + "out/", + "public/" + ], + "env": { + "browser": true, + "node": true, + "es6": true + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5211b43 --- /dev/null +++ b/.gitignore @@ -0,0 +1,42 @@ +# Node modules +node_modules/ +package-lock.json + +# Next.js build output +.next/ +out/ + +# Production build +build/ + +# Logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +# OS-specific files +.DS_Store +Thumbs.db + +# Vercel deployment +.vercel/ + +# Environment files (secrets — push to Coolify as app env vars instead) +.env +.env.local +.env.*.local + +# Optional: if using TypeScript +*.tsbuildinfo + +# Optional: if using testing tools +coverage/ +jest-test-results.json + +# Optional: if using IDEs +.vscode/ +.idea/ + +# Optional: if using TurboRepo or monorepo +turbo/ \ No newline at end of file diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..3d3333b --- /dev/null +++ b/.prettierignore @@ -0,0 +1,5 @@ +# .prettierignore +build +coverage +node_modules +public diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..3d14104 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,9 @@ +{ + "semi": true, + "tabWidth": 2, + "printWidth": 100, + "singleQuote": true, + "trailingComma": "es5", + "bracketSameLine": false, + "endOfLine": "auto" +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..c699dfd --- /dev/null +++ b/README.md @@ -0,0 +1,91 @@ +# Next.js + +A modern Next.js 15 application built with TypeScript and Tailwind CSS. + +## 🚀 Features + +- **Next.js 15** - Latest version with improved performance and features +- **React 19** - Latest React version with enhanced capabilities +- **Tailwind CSS** - Utility-first CSS framework for rapid UI development + +## 🛠️ Installation + +1. Install dependencies: + ```bash + npm install + # or + yarn install + ``` + +2. Start the development server: + ```bash + npm run dev + # or + yarn dev + ``` +3. Open [http://localhost:4028](http://localhost:4028) with your browser to see the result. + +## 📁 Project Structure + +``` +nextjs/ +├── public/ # Static assets +├── src/ +│ ├── app/ # App router components +│ │ ├── layout.tsx # Root layout component +│ │ └── page.tsx # Main page component +│ ├── components/ # Reusable UI components +│ ├── styles/ # Global styles and Tailwind configuration +├── next.config.mjs # Next.js configuration +├── package.json # Project dependencies and scripts +├── postcss.config.js # PostCSS configuration +└── tailwind.config.js # Tailwind CSS configuration + +``` + +## 🧩 Page Editing + +You can start editing the page by modifying `src/app/page.tsx`. The page auto-updates as you edit the file. + +## 🎨 Styling + +This project uses Tailwind CSS for styling with the following features: +- Utility-first approach for rapid development +- Custom theme configuration +- Responsive design utilities +- PostCSS and Autoprefixer integration + +## 📦 Available Scripts + +- `npm run dev` - Start development server on port 4028 +- `npm run build` - Build the application for production +- `npm run start` - Start the development server +- `npm run serve` - Start the production server +- `npm run lint` - Run ESLint to check code quality +- `npm run lint:fix` - Fix ESLint issues automatically +- `npm run format` - Format code with Prettier + +## 📱 Deployment + +Build the application for production: + + ```bash + npm run build + ``` + +## 📚 Learn More + +To learn more about Next.js, take a look at the following resources: + +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial + +You can check out the [Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! + +## 🙏 Acknowledgments + +- Built with [Rocket.new](https://rocket.new) +- Powered by Next.js and React +- Styled with Tailwind CSS + +Built with ❤️ on Rocket.new \ No newline at end of file diff --git a/config/ai-nodes.json b/config/ai-nodes.json new file mode 100644 index 0000000..f53ee87 --- /dev/null +++ b/config/ai-nodes.json @@ -0,0 +1,26 @@ +{ + "nodes": [ + { + "name": "AI_001", + "host": "68.248.192.64", + "port": 8765, + "api_key_env": "AI_NODE_API_KEY", + "models": [ + "llama3.1:70b", + "qwen2.5-coder:32b", + "deepseek-r1" + ] + }, + { + "name": "AI_002", + "host": "68.248.192.64", + "port": 8766, + "api_key_env": "AI_NODE_API_KEY", + "models": [ + "llama3.1:70b", + "qwen2.5-coder:32b", + "deepseek-r1" + ] + } + ] +} \ No newline at end of file diff --git a/image-hosts.config.js b/image-hosts.config.js new file mode 100644 index 0000000..579591e --- /dev/null +++ b/image-hosts.config.js @@ -0,0 +1,34 @@ +/** + * Image Hosts Configuration (add your image hosts here) + */ + +export const imageHosts = [ + { + protocol: 'https', + hostname: 'images.unsplash.com', + }, + { + protocol: 'https', + hostname: 'images.pexels.com', + }, + { + protocol: 'https', + hostname: 'images.pixabay.com', + }, + { + protocol: 'https', + hostname: 'img.rocket.new', + }, + { + protocol: 'https', + hostname: '*.supabase.co', + }, + { + protocol: 'https', + hostname: '*.supabase.in', + }, + { + protocol: 'https', + hostname: 'www.broadcastbeat.com', + }, +]; diff --git a/next-env.d.ts b/next-env.d.ts new file mode 100644 index 0000000..1b3be08 --- /dev/null +++ b/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/next.config.mjs b/next.config.mjs new file mode 100644 index 0000000..6ef1a95 --- /dev/null +++ b/next.config.mjs @@ -0,0 +1,133 @@ +import { imageHosts } from './image-hosts.config.js'; + +/** @type {import('next').NextConfig} */ +const nextConfig = { + productionBrowserSourceMaps: true, + distDir: process.env.DIST_DIR || '.next', + + typescript: { + ignoreBuildErrors: true, + }, + + eslint: { + ignoreDuringBuilds: true, + }, + + images: { + remotePatterns: imageHosts, + formats: ['image/avif', 'image/webp'], + minimumCacheTTL: 86400, + deviceSizes: [640, 750, 828, 1080, 1200, 1920], + imageSizes: [16, 32, 48, 64, 96, 128, 256, 384], + }, + + // Performance: compress responses + compress: true, + + // Performance: power headers for caching static assets + async headers() { + return [ + { + source: '/assets/:path*', + headers: [ + { key: 'Cache-Control', value: 'public, max-age=31536000, immutable' }, + ], + }, + { + source: '/_next/static/:path*', + headers: [ + { key: 'Cache-Control', value: 'public, max-age=31536000, immutable' }, + ], + }, + { + source: '/_next/image/:path*', + headers: [ + { key: 'Cache-Control', value: 'public, max-age=86400, stale-while-revalidate=604800' }, + ], + }, + ]; + }, + + async redirects() { + return [ + // Root redirect + { + source: '/', + destination: '/home-page', + permanent: false, + }, + // WordPress admin redirects for broadcastbeat.com (301 permanent) + { + source: '/wp-admin', + destination: '/admin', + permanent: true, + }, + { + source: '/wp-admin/', + destination: '/admin', + permanent: true, + }, + { + source: '/wp-admin/post-new.php', + destination: '/contributor', + permanent: true, + }, + { + source: '/wp-admin/edit.php', + destination: '/admin/articles', + permanent: true, + }, + { + source: '/wp-admin/profile.php', + destination: '/account', + permanent: true, + }, + { + source: '/wp-login.php', + destination: '/login', + permanent: true, + }, + { + source: '/wp-login.php/:path*', + destination: '/login', + permanent: true, + }, + // Catch-all for any other wp-admin sub-paths + { + source: '/wp-admin/:path*', + destination: '/admin', + permanent: true, + }, + // AV Beat dashboard redirect (avbeat.com/wp-admin/ → unified dashboard) + // These are handled by the same Next.js app when serving avbeat.com + { + source: '/avbeat/wp-admin', + destination: '/admin', + permanent: true, + }, + { + source: '/avbeat/wp-admin/', + destination: '/admin', + permanent: true, + }, + { + source: '/avbeat/wp-login.php', + destination: '/login', + permanent: true, + }, + { + source: '/avbeat/wp-login.php/:path*', + destination: '/login', + permanent: true, + }, + // Login aliases + { + source: '/login', + destination: '/login', + permanent: false, + }, + ]; + } +}; + +export default nextConfig; diff --git a/package.json b/package.json new file mode 100644 index 0000000..53765db --- /dev/null +++ b/package.json @@ -0,0 +1,96 @@ +{ + "name": "broadcastbeat", + "version": "0.1.0", + "private": true, + "rocketCritical": { + "dependencies": [ + "next", + "react", + "react-dom", + "@dhiwise/component-tagger", + "@tailwindcss/typography", + "recharts" + ], + "devDependencies": [ + "typescript", + "@types/node", + "@types/react", + "@types/react-dom", + "@typescript-eslint/eslint-plugin", + "@typescript-eslint/parser", + "eslint-config-next", + "tailwindcss", + "autoprefixer", + "postcss", + "eslint", + "prettier", + "@netlify/plugin-nextjs" + ], + "scripts": [ + "dev", + "build", + "start", + "lint", + "lint:fix", + "format", + "serve", + "type-check" + ], + "warning": "🚨 CRITICAL: DO NOT REMOVE OR MODIFY ABOVE DEPENDENCIES - Required for Next.js 15 TypeScript app functionality" + }, + "scripts": { + "dev": "next dev -p 4028", + "build": "next build", + "start": "next dev -p 4028", + "lint": "next lint", + "lint:fix": "next lint --fix", + "format": "prettier --write \"src/**/*.{ts,tsx,css,md,json}\"", + "serve": "next start", + "type-check": "tsc --noEmit" + }, + "dependencies": { + "@dhiwise/component-tagger": "^1.0.14", + "@heroicons/react": "^2.2.0", + "@rocketnew/llm-sdk": "^1.0.3", + "@supabase/ssr": "^0.6.1", + "@supabase/supabase-js": "^2.49.4", + "@tailwindcss/forms": "^0.5.10", + "@tailwindcss/typography": "^0.5.16", + "@tiptap/extension-color": "3.20.4", + "@tiptap/extension-image": "3.20.4", + "@tiptap/extension-link": "3.20.4", + "@tiptap/extension-placeholder": "3.20.4", + "@tiptap/extension-text-align": "3.20.4", + "@tiptap/extension-text-style": "3.20.4", + "@tiptap/extension-underline": "3.20.4", + "@tiptap/react": "3.20.4", + "@tiptap/starter-kit": "3.20.4", + "bcryptjs": "3.0.3", + "next": "15.1.11", + "nodemailer": "8.0.4", + "react": "19.0.3", + "react-dom": "19.0.3", + "recharts": "^2.15.2" + }, + "devDependencies": { + "@eslint/eslintrc": "^3", + "@netlify/plugin-nextjs": "^5.11.1", + "@types/bcryptjs": "2.4.6", + "@types/node": "^20", + "@types/nodemailer": "^6.4.17", + "@types/react": "19.0.3", + "@types/react-dom": "19.0.3", + "@typescript-eslint/eslint-plugin": "^8.29.0", + "@typescript-eslint/parser": "^8.29.0", + "autoprefixer": "10.4.2", + "eslint": "^9", + "eslint-config-next": "^15.2.4", + "eslint-config-prettier": "^10.1.1", + "eslint-plugin-prettier": "^5.2.6", + "postcss": "8.4.8", + "prettier": "^3.5.3", + "tailwindcss": "3.4.6", + "tailwindcss-animate": "^1.0.7", + "typescript": "^5" + } +} diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..96bb01e --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} \ No newline at end of file diff --git a/public/assets/images/no_image.png b/public/assets/images/no_image.png new file mode 100644 index 0000000000000000000000000000000000000000..b42dd2f4fe640c321aed37d1974679f31d554628 GIT binary patch literal 20735 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 literal 0 HcmV?d00001 diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..4eb44dd0bbb02aa4199dec871f72fa91ef239e5f GIT binary patch literal 4158 zcmchaKaWgN7>Cc;uoMgugs`)r*+e0sXNYg0(ym0Iu|c7-w?#)(5`Q8RS_KNlbR_ry z1ldYMn1*a(-RpT~?a+Q8|Jo$useh> z2MuU~d6m>VL-}Q%#4R`vO*jZ!p$1h*AN}-gf_>~a4_6?AKkySiK@0A|RX77jVJ~cf zL2}-8$G|%4^=W~=ZLp91F2ezMk;mrBtdRQwAK(?-g#;GhLOvRsciwgG!Md_nH-66C zeozf~2Hm-sqkZgG)INfzkV1^!`s#0=UfMy>xC zb>nIZgPSnyz2`dDTUUh+Ve%1r?XjG5o$ITT-vsKi6vlu(? zx~jDiL(jxwuk!Ctn0H+aox`->d28P9^kco0gn gP+KLLN;7Fqud-B6vm)(AniW|-k!EF97Sk-}ANdH0x&QzG literal 0 HcmV?d00001 diff --git a/src/app/about/page.tsx b/src/app/about/page.tsx new file mode 100644 index 0000000..9dd4a97 --- /dev/null +++ b/src/app/about/page.tsx @@ -0,0 +1,123 @@ +import React from "react"; +import type { Metadata } from "next"; +import Link from "next/link"; +import Header from "@/components/Header"; +import Footer from "@/components/Footer"; + +export const metadata: Metadata = { + title: "About BroadcastBeat — The Digital Platform for Broadcast Engineering", + description: + "BroadcastBeat is the leading digital platform for broadcast engineering professionals. Learn about our editorial mission, team, and commitment to the broadcast industry.", + alternates: { canonical: "/about" }, + openGraph: { + title: "About BroadcastBeat", + description: "BroadcastBeat is the leading digital platform for broadcast engineering professionals.", + url: "/about", + type: "website", + }, +}; + +const team = [ + { name: "Elena Vasquez", title: "Features Editor", bio: "Elena covers the intersection of technology and storytelling in broadcast media, with a focus on emerging production workflows and industry trends." }, + { name: "James Whitfield", title: "Senior Technology Correspondent", bio: "James has covered broadcast technology for over a decade, specializing in IP infrastructure, streaming, and the business of broadcast." }, + { name: "Sarah Chen", title: "Audio Technology Editor", bio: "Sarah brings deep expertise in professional audio to her coverage of mixing consoles, audio networking, and live production sound." }, + { name: "Rachel Kim", title: "Technology Analyst", bio: "Rachel focuses on data-driven coverage of industry trends, market analysis, and the business implications of new broadcast technologies." }, + { name: "Marcus Rivera", title: "Sports Technology Correspondent", bio: "Marcus covers the intersection of sports and broadcast technology, from remote production to interactive fan experiences." }, +]; + +export default function AboutPage() { + return ( +
+
+ + {/* DO NOT OVERRIDE — About page hero */} +
+
+
+ About +
+
+

About BroadcastBeat

+
+
+ +
+
+
+ {/* Mission */} +
+

Our Mission

+
+

+ BroadcastBeat is the digital platform of record for broadcast engineering professionals. We cover the technology, people, and business decisions that shape how the world's media is created, distributed, and consumed. +

+

+ From breaking news about the latest IP infrastructure deployments to in-depth reviews of broadcast cameras and audio consoles, BroadcastBeat provides the coverage that broadcast engineers, producers, and executives rely on to stay ahead of an industry in constant transformation. +

+

+ We are an official media partner of NAB Show and provide comprehensive coverage of all major broadcast industry events, including IBC, Cine Gear, and the Streaming Summit. +

+
+
+ + {/* Team */} +
+

Editorial Team

+
+ {team.map((member) => ( +
+
+ {member.name[0]} +
+
+

{member.name}

+

{member.title}

+

{member.bio}

+
+
+ ))} +
+
+ + {/* Contact */} +
+

Contact

+
+
+

Editorial

+ editorial@broadcastbeat.com +
+
+

Advertising Sales

+ jeff@broadcastbeat.com +
+
+

Press & Media

+ press@broadcastbeat.com +
+
+
+
+ + +
+
+
+
+ ); +} diff --git a/src/app/account/page.tsx b/src/app/account/page.tsx new file mode 100644 index 0000000..eb79d0b --- /dev/null +++ b/src/app/account/page.tsx @@ -0,0 +1,842 @@ +"use client"; +import React, { useState, useEffect } from "react"; +import Link from "next/link"; +import Header from "@/components/Header"; +import Footer from "@/components/Footer"; +import AppImage from "@/components/ui/AppImage"; +import { createClient } from "@/lib/supabase/client"; +import { useRouter } from "next/navigation"; + +interface UserProfile { + id: string; + email: string; + full_name: string; + avatar_url: string | null; + bio: string | null; + website: string | null; + location: string | null; + reputation: number; + created_at: string; +} + +interface BookmarkItem { + id: string; + article_slug: string; + article_title: string; + article_excerpt: string | null; + article_image: string | null; + article_image_alt: string | null; + article_category: string | null; + article_author: string | null; + article_read_time: string | null; + article_date: string | null; + saved_at: string; +} + +interface HistoryItem { + id: string; + article_slug: string; + article_title: string; + article_excerpt: string | null; + article_image: string | null; + article_image_alt: string | null; + article_category: string | null; + article_author: string | null; + article_read_time: string | null; + article_date: string | null; + viewed_at: string; +} + +interface TopicPref { + id: string; + topic: string; + view_count: number; + last_viewed_at: string; +} + +type ActiveTab = "profile" | "security" | "bookmarks" | "history" | "topics" | "preferences"; + +export default function AccountPage() { + const router = useRouter(); + const supabase = createClient(); + + const [user, setUser] = useState(null); + const [profile, setProfile] = useState(null); + const [loading, setLoading] = useState(true); + const [saving, setSaving] = useState(false); + const [activeTab, setActiveTab] = useState("profile"); + const [toast, setToast] = useState<{ message: string; type: "success" | "error" } | null>(null); + + // Profile form state + const [fullName, setFullName] = useState(""); + const [bio, setBio] = useState(""); + const [website, setWebsite] = useState(""); + const [location, setLocation] = useState(""); + + // Security form state + const [newPassword, setNewPassword] = useState(""); + const [confirmPassword, setConfirmPassword] = useState(""); + const [changingPassword, setChangingPassword] = useState(false); + + // Digest preference state + const [weeklyDigest, setWeeklyDigest] = useState(false); + const [savingDigest, setSavingDigest] = useState(false); + + // Bookmarks + const [bookmarks, setBookmarks] = useState([]); + const [bookmarksLoading, setBookmarksLoading] = useState(false); + + // Reading History + const [history, setHistory] = useState([]); + const [historyLoading, setHistoryLoading] = useState(false); + + // Topic Preferences + const [topics, setTopics] = useState([]); + const [topicsLoading, setTopicsLoading] = useState(false); + + const showToast = (message: string, type: "success" | "error") => { + setToast({ message, type }); + setTimeout(() => setToast(null), 3500); + }; + + useEffect(() => { + const init = async () => { + const { data: { user: authUser } } = await supabase.auth.getUser(); + if (!authUser) { + router.push("/login"); + return; + } + setUser(authUser); + + const { data: profileData } = await supabase + .from("user_profiles") + .select("*") + .eq("id", authUser.id) + .maybeSingle(); + + if (profileData) { + setProfile(profileData); + setFullName(profileData.full_name || ""); + setBio(profileData.bio || ""); + setWebsite(profileData.website || ""); + setLocation(profileData.location || ""); + } + setLoading(false); + }; + init(); + }, []); + + useEffect(() => { + if (!user) return; + fetch("/api/digest/preferences") + .then((r) => r.json()) + .then((d) => setWeeklyDigest(d.weekly_digest_enabled ?? false)) + .catch(() => {}); + }, [user]); + + // Load bookmarks when tab is active + useEffect(() => { + if (activeTab !== "bookmarks" || !user) return; + setBookmarksLoading(true); + supabase + .from("reading_list") + .select("*") + .eq("user_id", user.id) + .order("saved_at", { ascending: false }) + .then(({ data }) => { + setBookmarks(data || []); + setBookmarksLoading(false); + }); + }, [activeTab, user]); + + // Load reading history when tab is active + useEffect(() => { + if (activeTab !== "history" || !user) return; + setHistoryLoading(true); + supabase + .from("reading_history") + .select("*") + .eq("user_id", user.id) + .order("viewed_at", { ascending: false }) + .limit(50) + .then(({ data }) => { + setHistory(data || []); + setHistoryLoading(false); + }); + }, [activeTab, user]); + + // Load topic preferences when tab is active + useEffect(() => { + if (activeTab !== "topics" || !user) return; + setTopicsLoading(true); + supabase + .from("user_topic_preferences") + .select("*") + .eq("user_id", user.id) + .order("view_count", { ascending: false }) + .then(({ data }) => { + setTopics(data || []); + setTopicsLoading(false); + }); + }, [activeTab, user]); + + const handleSaveProfile = async (e: React.FormEvent) => { + e.preventDefault(); + if (!user) return; + setSaving(true); + try { + const { error } = await supabase + .from("user_profiles") + .upsert({ + id: user.id, + email: user.email, + full_name: fullName, + bio, + website, + location, + }); + if (error) throw error; + showToast("Profile updated successfully", "success"); + } catch (err: any) { + showToast(err?.message || "Failed to update profile", "error"); + } finally { + setSaving(false); + } + }; + + const handleChangePassword = async (e: React.FormEvent) => { + e.preventDefault(); + if (newPassword !== confirmPassword) { + showToast("Passwords do not match", "error"); + return; + } + if (newPassword.length < 6) { + showToast("Password must be at least 6 characters", "error"); + return; + } + setChangingPassword(true); + try { + const { error } = await supabase.auth.updateUser({ password: newPassword }); + if (error) throw error; + setNewPassword(""); + setConfirmPassword(""); + showToast("Password updated successfully", "success"); + } catch (err: any) { + showToast(err?.message || "Failed to update password", "error"); + } finally { + setChangingPassword(false); + } + }; + + const handleSignOut = async () => { + await supabase.auth.signOut(); + router.push("/home-page"); + }; + + const handleToggleDigest = async (enabled: boolean) => { + setSavingDigest(true); + try { + const res = await fetch("/api/digest/preferences", { + method: "PATCH", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ weekly_digest_enabled: enabled }), + }); + if (!res.ok) throw new Error("Failed to update"); + setWeeklyDigest(enabled); + showToast(enabled ? "Weekly digest enabled" : "Weekly digest disabled", "success"); + } catch { + showToast("Failed to update digest preference", "error"); + } finally { + setSavingDigest(false); + } + }; + + const handleRemoveBookmark = async (id: string, slug: string) => { + if (!user) return; + try { + await supabase.from("reading_list").delete().eq("id", id).eq("user_id", user.id); + setBookmarks((prev) => prev.filter((b) => b.id !== id)); + showToast("Bookmark removed", "success"); + } catch { + showToast("Failed to remove bookmark", "error"); + } + }; + + const handleClearHistory = async () => { + if (!user) return; + try { + await supabase.from("reading_history").delete().eq("user_id", user.id); + setHistory([]); + showToast("Reading history cleared", "success"); + } catch { + showToast("Failed to clear history", "error"); + } + }; + + const tabs: { id: ActiveTab; label: string }[] = [ + { id: "profile", label: "Profile" }, + { id: "security", label: "Security" }, + { id: "bookmarks", label: "Bookmarks" }, + { id: "history", label: "History" }, + { id: "topics", label: "Topics" }, + { id: "preferences", label: "Preferences" }, + ]; + + if (loading) { + return ( +
+
+
+
+
+
+
+
+
+
+ ); + } + + return ( +
+
+ + {/* Toast */} + {toast && ( +
+ {toast.type === "success" ? ( + + ) : ( + + )} + {toast.message} +
+ )} + + {/* Breadcrumb */} +
+
+ +
+
+ +
+
+ {/* Page Header */} +
+
+

Account Settings

+

{user?.email}

+
+ +
+ + {/* Tabs */} +
+ {tabs.map((tab) => ( + + ))} +
+ + {/* Profile Tab */} + {activeTab === "profile" && ( +
+
+

+ Public Profile +

+
+
+ + setFullName(e.target.value)} + placeholder="Your full name" + className="w-full bg-[#111111] border border-[#2a2a2a] text-[#e0e0e0] font-body text-sm px-3 py-2.5 rounded-sm focus:outline-none focus:border-[#3b82f6] focus:ring-1 focus:ring-[#3b82f6] transition-colors placeholder-[#444]" + /> +
+
+ +