diff --git a/public/notifications.xml b/public/notifications.xml index 8ab9835b..28c9f676 100644 --- a/public/notifications.xml +++ b/public/notifications.xml @@ -257,7 +257,7 @@ It will improve uptime for FED API and faster EU streams, another proxy which we If you are interested in donating, please check the link below! - https://rentry.co/h5mypdfs + https://rentry.co/nnqtas3e Sat, 06 Sep 2025 14:42:00 MST announcement diff --git a/src/assets/locales/en.json b/src/assets/locales/en.json index af290b9a..36de4fc0 100644 --- a/src/assets/locales/en.json +++ b/src/assets/locales/en.json @@ -392,6 +392,18 @@ "It's the Great Pumpkin, Charlie Brown!" ] } + }, + "support": { + "title": "P-Stream needs your help!", + "description": "P-Stream is run at a loss, and we need help to keep it ad free! If you enjoy using P-Stream, please consider donating to help us cover our costs.", + "moreInfo": "More info", + "explanation": "If you aren't using the extension or don't have FED API set up, it may be harder to find content! We want to fix this, but it's a lot harder to provide content without expensive servers. So please, if you enjoy using P-Stream, please consider donating to help us cover our growing costs.", + "explanation2": "If you want more info, please join our ", + "discord": "Discord", + "thankYou": "Thank you for your support!", + "donate": "Donate", + "label": "Project Funding: ${{current}} / ${{goal}}", + "complete": "complete" } }, "media": { diff --git a/src/components/LinksDropdown.tsx b/src/components/LinksDropdown.tsx index d4338b4b..50ae38c0 100644 --- a/src/components/LinksDropdown.tsx +++ b/src/components/LinksDropdown.tsx @@ -315,7 +315,7 @@ export function LinksDropdown(props: { children: React.ReactNode }) { /> diff --git a/src/components/layout/Footer.tsx b/src/components/layout/Footer.tsx index 458f2294..5eb71c42 100644 --- a/src/components/layout/Footer.tsx +++ b/src/components/layout/Footer.tsx @@ -83,7 +83,7 @@ export function Footer() { {t("footer.links.discord")} - + {t("footer.links.funding")}
diff --git a/src/components/overlays/SupportInfoModal.tsx b/src/components/overlays/SupportInfoModal.tsx new file mode 100644 index 00000000..573af03c --- /dev/null +++ b/src/components/overlays/SupportInfoModal.tsx @@ -0,0 +1,40 @@ +import { useTranslation } from "react-i18next"; + +import { FancyModal } from "./Modal"; +import { Button } from "../buttons/Button"; +import { MwLink } from "../text/Link"; + +export function SupportInfoModal({ id }: { id: string }) { + const { t } = useTranslation(); + + return ( + +
+

{t("home.support.explanation")}

+

+ {t("home.support.explanation2")}{" "} + + {t("home.support.discord")} + +

+ +
+ + + +
+ +
+ {t("home.support.thankYou")} +
+
+
+ ); +} diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx index 88a97ee0..ddd0bf92 100644 --- a/src/pages/HomePage.tsx +++ b/src/pages/HomePage.tsx @@ -25,6 +25,7 @@ import { MediaItem } from "@/utils/mediaTypes"; import { Button } from "./About"; import { AdsPart } from "./parts/home/AdsPart"; +import { SupportBar } from "./parts/home/SupportBar"; function useSearch(search: string) { const [searching, setSearching] = useState(false); @@ -171,6 +172,8 @@ export function HomePage() { /> )} + {conf().SHOW_SUPPORT_BAR ? : null} + {conf().SHOW_AD ? : null}
diff --git a/src/pages/parts/home/SupportBar.tsx b/src/pages/parts/home/SupportBar.tsx new file mode 100644 index 00000000..7f170826 --- /dev/null +++ b/src/pages/parts/home/SupportBar.tsx @@ -0,0 +1,131 @@ +import { useCallback, useState } from "react"; +import { useTranslation } from "react-i18next"; + +import { Icon, Icons } from "@/components/Icon"; +import { SettingsCard } from "@/components/layout/SettingsCard"; +import { MwLink } from "@/components/text/Link"; +import { Heading3 } from "@/components/utils/Text"; +import { conf } from "@/setup/config"; +import { useOverlayStack } from "@/stores/interface/overlayStack"; + +function getCookie(name: string): string | null { + const cookies = document.cookie.split(";"); + for (let i = 0; i < cookies.length; i += 1) { + const cookie = cookies[i].trim(); + if (cookie.startsWith(`${name}=`)) { + return cookie.substring(name.length + 1); + } + } + return null; +} + +function setCookie(name: string, value: string, expiryDays: number): void { + const date = new Date(); + date.setTime(date.getTime() + expiryDays * 24 * 60 * 60 * 1000); + const expires = `expires=${date.toUTCString()}`; + document.cookie = `${name}=${value};${expires};path=/`; +} + +export function SupportBar() { + const { t } = useTranslation(); + const { showModal } = useOverlayStack(); + const [isDescriptionDismissed, setIsDescriptionDismissed] = useState(() => { + return getCookie("supportDescriptionDismissed") === "true"; + }); + + const toggleDescription = useCallback(() => { + const newState = !isDescriptionDismissed; + setIsDescriptionDismissed(newState); + setCookie("supportDescriptionDismissed", newState ? "true" : "false", 14); // Expires after 14 days + }, [isDescriptionDismissed]); + + const openSupportModal = useCallback(() => { + showModal("support-info"); + }, [showModal]); + + const supportValue = conf().SUPPORT_BAR_VALUE; + if (!supportValue) return null; + + // Parse fraction like "100/300" + const [currentStr, goalStr] = supportValue.split("/"); + const current = parseFloat(currentStr) || 0; + const goal = parseFloat(goalStr) || 1; + + const percentage = Math.min((current / goal) * 100, 100); + + return ( +
+
+ + +
+ + {t("home.support.title")} + +

+ {t("home.support.description")} +

+
+
+ + {t("home.support.label", { + current: current.toLocaleString(), + goal: goal.toLocaleString(), + })} + + + {percentage.toFixed(1)}% {t("home.support.complete")} + +
+
+
+ {/* Progress bar */} +
+
+
+
+ + + + + + {t("home.support.donate")} + + +
+ +
+
+ ); +} diff --git a/src/setup/App.tsx b/src/setup/App.tsx index ad08441c..ff6bf327 100644 --- a/src/setup/App.tsx +++ b/src/setup/App.tsx @@ -14,6 +14,7 @@ import { generateQuickSearchMediaUrl } from "@/backend/metadata/tmdb"; import { DetailsModal } from "@/components/overlays/detailsModal"; import { KeyboardCommandsModal } from "@/components/overlays/KeyboardCommandsModal"; import { NotificationModal } from "@/components/overlays/notificationsModal"; +import { SupportInfoModal } from "@/components/overlays/SupportInfoModal"; import { useGlobalKeyboardEvents } from "@/hooks/useGlobalKeyboardEvents"; import { useOnlineListener } from "@/hooks/usePing"; import { AboutPage } from "@/pages/About"; @@ -126,6 +127,7 @@ function App() { + diff --git a/src/setup/config.ts b/src/setup/config.ts index d5ac0c81..2f862009 100644 --- a/src/setup/config.ts +++ b/src/setup/config.ts @@ -34,6 +34,8 @@ interface Config { BANNER_ID: string; USE_TRAKT: boolean; HIDE_PROXY_ONBOARDING: boolean; + SHOW_SUPPORT_BAR: boolean; + SUPPORT_BAR_VALUE: string; } export interface RuntimeConfig { @@ -64,6 +66,8 @@ export interface RuntimeConfig { BANNER_ID: string | null; USE_TRAKT: boolean; HIDE_PROXY_ONBOARDING: boolean; + SHOW_SUPPORT_BAR: boolean; + SUPPORT_BAR_VALUE: string; } const env: Record = { @@ -97,6 +101,8 @@ const env: Record = { BANNER_ID: import.meta.env.VITE_BANNER_ID, USE_TRAKT: import.meta.env.VITE_USE_TRAKT, HIDE_PROXY_ONBOARDING: import.meta.env.VITE_HIDE_PROXY_ONBOARDING, + SHOW_SUPPORT_BAR: import.meta.env.VITE_SHOW_SUPPORT_BAR, + SUPPORT_BAR_VALUE: import.meta.env.VITE_SUPPORT_BAR_VALUE, }; function coerceUndefined(value: string | null | undefined): string | undefined { @@ -173,5 +179,7 @@ export function conf(): RuntimeConfig { BANNER_ID: getKey("BANNER_ID"), USE_TRAKT: getKey("USE_TRAKT", "false") === "true", HIDE_PROXY_ONBOARDING: getKey("HIDE_PROXY_ONBOARDING", "false") === "true", + SHOW_SUPPORT_BAR: getKey("SHOW_SUPPORT_BAR", "false") === "true", + SUPPORT_BAR_VALUE: getKey("SUPPORT_BAR_VALUE") ?? "", }; }