update popup modal logic

This commit is contained in:
Pas 2025-03-29 12:20:04 -06:00
parent 520c32235c
commit a02ea7c684
4 changed files with 230 additions and 284 deletions

View file

@ -1,7 +1,12 @@
import { ReactNode, useCallback } from "react"; import classNames from "classnames";
import { ReactNode, useCallback, useEffect } from "react";
import { Helmet } from "react-helmet-async"; import { Helmet } from "react-helmet-async";
import { IconPatch } from "@/components/buttons/IconPatch";
import { Icons } from "@/components/Icon";
import { OverlayPortal } from "@/components/overlays/OverlayDisplay"; import { OverlayPortal } from "@/components/overlays/OverlayDisplay";
import { Flare } from "@/components/utils/Flare";
import { Heading2 } from "@/components/utils/Text";
import { useQueryParam } from "@/hooks/useQueryParams"; import { useQueryParam } from "@/hooks/useQueryParams";
export function useModal(id: string) { export function useModal(id: string) {
@ -40,3 +45,74 @@ export function Modal(props: { id: string; children?: ReactNode }) {
</OverlayPortal> </OverlayPortal>
); );
} }
export function FancyModal(props: {
id: string;
children?: ReactNode;
title?: string;
size?: "md" | "xl";
oneTime?: boolean;
}) {
const modal = useModal(props.id);
useEffect(() => {
if (props.oneTime) {
const isDismissed = localStorage.getItem(`modal-${props.id}-dismissed`);
if (!isDismissed) {
modal.show();
}
}
}, [modal, props.id, props.oneTime]);
const handleClose = () => {
if (props.oneTime) {
localStorage.setItem(`modal-${props.id}-dismissed`, "true");
}
modal.hide();
};
return (
<OverlayPortal darken close={handleClose} show={modal.isShown}>
<Helmet>
<html data-no-scroll />
</Helmet>
<div className="flex absolute inset-0 items-center justify-center">
<Flare.Base
className={classNames(
"group -m-[0.705em] rounded-3xl bg-background-main transition-colors duration-300 focus:relative focus:z-10",
"w-full mx-4 p-6 bg-mediaCard-hoverBackground bg-opacity-60 backdrop-filter backdrop-blur-lg shadow-lg",
props.size === "md" ? "max-w-md" : "max-w-2xl",
)}
>
<div className="transition-transform duration-300 overflow-y-scroll max-h-[90dvh] scrollbar-none">
<Flare.Light
flareSize={300}
cssColorVar="--colors-mediaCard-hoverAccent"
backgroundClass="bg-mediaCard-hoverBackground duration-100"
className="rounded-3xl bg-background-main group-hover:opacity-100"
/>
<Flare.Child className="pointer-events-auto relative mb-2p-[0.4em] transition-transform duration-300">
<div className="flex justify-between items-center mb-4">
{props.title && (
<Heading2 className="!mt-0 !mb-0 pr-6">
{props.title}
</Heading2>
)}
<button
type="button"
className="text-s font-semibold text-type-secondary hover:text-white transition-transform hover:scale-95"
onClick={handleClose}
>
<IconPatch icon={Icons.X} />
</button>
</div>
<div className="text-lg text-type-secondary">
{props.children}
</div>
</Flare.Child>
</div>
</Flare.Base>
</div>
</OverlayPortal>
);
}

View file

@ -50,6 +50,7 @@ export function HomePage() {
const s = useSearch(search); const s = useSearch(search);
const [showBookmarks, setShowBookmarks] = useState(false); const [showBookmarks, setShowBookmarks] = useState(false);
const [showWatching, setShowWatching] = useState(false); const [showWatching, setShowWatching] = useState(false);
// const modal = useModal("notice");
const handleClick = (path: To) => { const handleClick = (path: To) => {
window.scrollTo(0, 0); window.scrollTo(0, 0);
@ -58,48 +59,12 @@ export function HomePage() {
const enableDiscover = usePreferencesStore((state) => state.enableDiscover); const enableDiscover = usePreferencesStore((state) => state.enableDiscover);
/*
// Safari Notice
const [showModal, setShowModal] = useState(() => {
const isSafari =
typeof navigator !== "undefined" &&
/Safari/.test(navigator.userAgent) &&
!/Chrome/.test(navigator.userAgent);
const isMac =
typeof navigator !== "undefined" && /Mac/.test(navigator.platform);
const isIOS =
typeof navigator !== "undefined" &&
/iPhone|iPad|iPod/.test(navigator.userAgent);
return isSafari && (isMac || isIOS);
});
*/
/* One time notice
const [showModal, setShowModal] = useState(false);
useEffect(() => {
const isDismissed = localStorage.getItem("popupDismissed");
if (!isDismissed) {
setShowModal(true);
}
}, []);
const handleCloseModal = () => {
setShowModal(false);
localStorage.setItem("popupDismissed", "true");
};
*/
// const { loggedIn } = useAuth(); // Adjust padding for popup show button based on logged in state // const { loggedIn } = useAuth(); // Adjust padding for popup show button based on logged in state
return ( return (
<HomeLayout showBg={showBg}> <HomeLayout showBg={showBg}>
{/* Popup show button {/* <a
<a onClick={() => modal.show()}
onClick={() => setShowModal(true)}
className={` text-white tabbable rounded-full z-50 fixed top-5 ${ className={` text-white tabbable rounded-full z-50 fixed top-5 ${
loggedIn loggedIn
? "right-[7.5rem] lg:right-[12.5rem] lg:text-2xl" ? "right-[7.5rem] lg:right-[12.5rem] lg:text-2xl"
@ -110,8 +75,7 @@ export function HomePage() {
<IconPill icon={Icons.WARNING}> <IconPill icon={Icons.WARNING}>
<span className="font-bold select-none">READ</span> <span className="font-bold select-none">READ</span>
</IconPill> </IconPill>
</a> </a> */}
*/}
<div className="mb-16 sm:mb-24"> <div className="mb-16 sm:mb-24">
<Helmet> <Helmet>
<style type="text/css">{` <style type="text/css">{`
@ -122,100 +86,96 @@ export function HomePage() {
<title>{t("global.name")}</title> <title>{t("global.name")}</title>
</Helmet> </Helmet>
{/* Popup {/* Popup
{showModal && ( <FancyModal
<PopupModal id="notice"
styles="max-w-2xl" // max-w-md for short title="We're changing our backend server!"
title="Were changing our backend server!" oneTime
message={ >
<div> <div>
<p> <p>
On <strong>January 8th</strong>, the backend server will On <strong>January 8th</strong>, the backend server will change
change from: from:
</p> </p>
<p> <p>
<strong>server.vidbinge.com</strong> {" "} <strong>server.vidbinge.com</strong> {" "}
<strong>server.fifthwit.tech</strong> <strong>server.fifthwit.tech</strong>
</p> </p>
<br /> <br />
<p> <p>
You will need to <strong>migrate your account </strong> to the You will need to <strong>migrate your account </strong> to the new
new server or choose to continue using the old server by server or choose to continue using the old server by updating your
updating your settings. settings.
</p> </p>
<br /> <br />
<p> <p>
<strong>What You Need to Know:</strong> <strong>What You Need to Know:</strong>
</p> </p>
<ul> <ul>
<li> <li>
1. <strong>Migrating Your Account:</strong> Your data (e.g., 1. <strong>Migrating Your Account:</strong> Your data (e.g.,
bookmarks) will not be automatically transferred. Youll bookmarks) will not be automatically transferred. You&apos;ll
need to migrate your account from the settings page. Or from need to migrate your account from the settings page. Or from
below. below.
</li> </li>
<li> <li>
2. <strong>Staying on the Old Server:</strong> If you dont 2. <strong>Staying on the Old Server:</strong> If you don&apos;t
want to change to the new server, your data will remain safe want to change to the new server, your data will remain safe on{" "}
on <strong>server.vidbinge.com</strong>. You can change the <strong>server.vidbinge.com</strong>. You can change the Backend
Backend URL in your settings to URL in your settings to &quot;https://server.vidbinge.com&quot;.
&quot;https://server.vidbinge.com&quot;. </li>
</li> </ul>
</ul> <br />
<br /> <p>
<p> <strong>Steps to Move Your Data:</strong>
<strong>Steps to Move Your Data:</strong> </p>
</p> <ol>
<ol> <li>
<li> 1. Log into your account on <strong>server.vidbinge.com</strong>
1. Log into your account on{" "} .
<strong>server.vidbinge.com</strong>. </li>
</li> <li>
<li> (If you already are logged in, press here:{" "}
(If you already are logged in, press here:{" "} <a href="/migration" className="text-type-link">
<a href="/migration" className="text-type-link"> Migrate my data.
Migrate my data. </a>
</a> )
) </li>
</li> <li>
<li> 2. Go to the <strong>Settings</strong> page.
2. Go to the <strong>Settings</strong> page. </li>
</li> <li>
<li> 3. Scroll down to{" "}
3. Scroll down to{" "} <strong>Connections &gt; Custom Server</strong>.
<strong>Connections &gt; Custom Server</strong>. </li>
</li> <li>
<li> 3. Press the &quot;Migrate my data to a new server.&quot;
3. Press the &quot;Migrate my data to a new server.&quot; button.
button. </li>
</li> <li>
<li> 4. Enter the new server url:{" "}
4. Enter the new server url:{" "} <strong>https://server.fifthwit.tech</strong> and press
<strong>https://server.fifthwit.tech</strong> and press &quot;Migrate&quot;.
&quot;Migrate&quot;. </li>
</li> <li>5. Login to your account with the same passphrase!</li>
<li>5. Login to your account with the same passphrase!</li> </ol>
</ol> <br />
<br /> <p>
<p> Thank you for your understanding and support during this
Thank you for your understanding and support during this transition! If you have questions or need help, feel free to reach
transition! If you have questions or need help, feel free to out on the{" "}
reach out on the{" "} <a
<a href="https://discord.com/invite/7z6znYgrTG"
href="https://discord.com/invite/7z6znYgrTG" target="_blank"
target="_blank" rel="noopener noreferrer"
rel="noopener noreferrer" className="text-type-link"
className="text-type-link" >
> P-Stream Discord
P-Stream Discord </a>
</a> !
! </p>
</p> </div>
</div> </FancyModal>
}
onClose={handleCloseModal}
/>
)}
*/} */}
<HeroPart searchParams={searchParams} setIsSticky={setShowBg} /> <HeroPart searchParams={searchParams} setIsSticky={setShowBg} />

View file

@ -8,7 +8,12 @@ import { SettingsCard } from "@/components/layout/SettingsCard";
import { Stepper } from "@/components/layout/Stepper"; import { Stepper } from "@/components/layout/Stepper";
import { BiggerCenterContainer } from "@/components/layout/ThinContainer"; import { BiggerCenterContainer } from "@/components/layout/ThinContainer";
import { VerticalLine } from "@/components/layout/VerticalLine"; import { VerticalLine } from "@/components/layout/VerticalLine";
import { Modal, ModalCard, useModal } from "@/components/overlays/Modal"; import {
FancyModal,
Modal,
ModalCard,
useModal,
} from "@/components/overlays/Modal";
import { import {
StatusCircle, StatusCircle,
StatusCircleProps, StatusCircleProps,
@ -33,7 +38,6 @@ import { conf } from "@/setup/config";
import { useAuthStore } from "@/stores/auth"; import { useAuthStore } from "@/stores/auth";
import { getProxyUrls } from "@/utils/proxyUrls"; import { getProxyUrls } from "@/utils/proxyUrls";
import { PopupModal } from "../parts/home/PopupModal";
import { Status, testFebboxToken } from "../parts/settings/SetupPart"; import { Status, testFebboxToken } from "../parts/settings/SetupPart";
async function getFebboxTokenStatus(febboxToken: string | null) { async function getFebboxTokenStatus(febboxToken: string | null) {
@ -179,6 +183,7 @@ export function FEDAPISetup() {
export function OnboardingPage() { export function OnboardingPage() {
const navigate = useNavigateOnboarding(); const navigate = useNavigateOnboarding();
const skipModal = useModal("skip"); const skipModal = useModal("skip");
const infoModal = useModal("info");
const { completeAndRedirect } = useRedirectBack(); const { completeAndRedirect } = useRedirectBack();
const { t } = useTranslation(); const { t } = useTranslation();
const noProxies = getProxyUrls().length === 0; const noProxies = getProxyUrls().length === 0;
@ -189,12 +194,6 @@ export function OnboardingPage() {
!/Chrome/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent) &&
!/Edg/.test(navigator.userAgent); !/Edg/.test(navigator.userAgent);
const [showModal, setShowModal] = useState(false);
const handleCloseModal = () => {
setShowModal(false);
};
return ( return (
<MinimalPageLayout> <MinimalPageLayout>
<PageTitle subpage k="global.pages.onboarding" /> <PageTitle subpage k="global.pages.onboarding" />
@ -216,68 +215,59 @@ export function OnboardingPage() {
</div> </div>
</ModalCard> </ModalCard>
</Modal> </Modal>
{showModal && ( <FancyModal id={infoModal.id} title="Understanding a setup" size="xl">
<PopupModal <div>
styles="max-w-2xl" // max-w-md for short max-w-2xl long <p>
title="Understanding a setup" P-Stream doesn&apos;t host videos. It relies on third-party websites
message={ for content, so you need to choose how it connects to those sites.
<div> <br />
<p> <br />
P-Stream doesn&apos;t host videos. It relies on third-party <strong>Your Options:</strong>
websites for content, so you need to choose how it connects to <br />
those sites. <strong>1. Extension (Recommended)</strong>
<br />
The extension gives you access to the most sources. It acts as a
local proxy and can handle sites that need special cookies or
headers to load.
<br />
<br />
<strong>2. Proxy</strong>
<br />
The proxy scrapes media from other websites. It bypasses browser
restrictions (like CORS) to allow scraping.
<br />
<br />
<strong>3. Default Setup</strong>
<br />
Uses P-Stream&apos;s built-in proxy. It&apos;s the easiest option
but might be slower due to shared bandwidth.
<br />
<br />
{conf().ALLOW_FEBBOX_KEY && (
<>
<strong>Optional FED API (Febbox) UI token</strong>
<br />
Bringing your own Febbox account allows you to unlock FED API,
our best source with 4K quality, Dolby Atmos, the most content,
and the best (fastest) load times. This the highly recommended!
<br /> <br />
<br /> <br />
<strong>Your Options:</strong> </>
<br /> )}
<strong>1. Extension (Recommended)</strong> If you have more questions on how this works, feel free to ask on
<br /> the{" "}
The extension gives you access to the most sources. It acts as a <a
local proxy and can handle sites that need special cookies or href="https://discord.com/invite/7z6znYgrTG"
headers to load. target="_blank"
<br /> rel="noopener noreferrer"
<br /> className="text-type-link"
<strong>2. Proxy</strong> >
<br /> P-Stream Discord
The proxy scrapes media from other websites. It bypasses browser </a>{" "}
restrictions (like CORS) to allow scraping. server!
<br /> </p>
<br /> </div>
<strong>3. Default Setup</strong> </FancyModal>
<br />
Uses P-Stream&apos;s built-in proxy. It&apos;s the easiest
option but might be slower due to shared bandwidth.
<br />
<br />
{conf().ALLOW_FEBBOX_KEY && (
<>
<strong>Optional FED API (Febbox) UI token</strong>
<br />
Bringing your own Febbox account allows you to unlock FED
API, our best source with 4K quality, Dolby Atmos, the most
content, and the best (fastest) load times. This the highly
recommended!
<br />
<br />
</>
)}
If you have more questions on how this works, feel free to ask
on the{" "}
<a
href="https://discord.com/invite/7z6znYgrTG"
target="_blank"
rel="noopener noreferrer"
className="text-type-link"
>
P-Stream Discord
</a>{" "}
server!
</p>
</div>
}
onClose={handleCloseModal}
/>
)}
<BiggerCenterContainer> <BiggerCenterContainer>
<Stepper steps={2} current={1} className="mb-12" /> <Stepper steps={2} current={1} className="mb-12" />
<Heading2 className="!mt-0 !text-3xl"> <Heading2 className="!mt-0 !text-3xl">
@ -287,7 +277,7 @@ export function OnboardingPage() {
{t("onboarding.start.explainer")} {t("onboarding.start.explainer")}
<div <div
className="pt-4 flex cursor-pointer items-center text-type-link" className="pt-4 flex cursor-pointer items-center text-type-link"
onClick={() => setShowModal(true)} onClick={() => infoModal.show()}
> >
<p>More info</p> <p>More info</p>
<Icon className="pl-2" icon={Icons.CIRCLE_QUESTION} /> <Icon className="pl-2" icon={Icons.CIRCLE_QUESTION} />

View file

@ -1,80 +0,0 @@
import classNames from "classnames";
import { ReactNode, useEffect } from "react";
import { IconPatch } from "@/components/buttons/IconPatch";
import { Icons } from "@/components/Icon";
import { Flare } from "@/components/utils/Flare";
import { Heading2 } from "@/components/utils/Text";
export interface PopupModalProps {
title: string;
message: ReactNode;
closable?: boolean;
onClose?: () => void;
styles?: string;
}
export function PopupModal({
title,
message,
closable = true,
onClose,
styles,
}: PopupModalProps) {
useEffect(() => {
document.body.style.overflow = "hidden";
return () => {
document.body.style.overflow = "";
};
}, []);
return (
<div
className={classNames(
"fixed inset-0 z-[100] flex items-center justify-center",
"bg-background-main bg-opacity-75 backdrop-filter backdrop-blur-sm",
"transition-opacity duration-400",
"pointer-events-auto",
)}
onClick={onClose}
>
<Flare.Base
className={classNames(
"group -m-[0.705em] rounded-3xl bg-background-main transition-colors duration-300 focus:relative focus:z-10",
"fixed top-0 left-0 right-0 z-50 p-6 bg-mediaCard-hoverBackground bg-opacity-60 backdrop-filter backdrop-blur-lg shadow-lg mx-auto",
)}
>
<div
className={classNames(
"transition-transform duration-300",
"overflow-y-scroll max-h-[90dvh] md:max-h-[90dvh] scrollbar-none",
styles,
)}
>
<Flare.Light
flareSize={300}
cssColorVar="--colors-mediaCard-hoverAccent"
backgroundClass="bg-mediaCard-hoverBackground duration-100"
className="rounded-3xl bg-background-main group-hover:opacity-100"
/>
<Flare.Child className="pointer-events-auto relative mb-2p-[0.4em] transition-transform duration-300">
<div className="flex justify-between items-center mb-4">
<Heading2 className="!mt-0 !mb-0 pr-6">{title}</Heading2>
{closable && (
<button
type="button"
className="fixed right-4 text-s font-semibold text-type-secondary hover:text-white transition-transform hover:scale-110"
onClick={onClose}
>
<IconPatch icon={Icons.X} />
</button>
)}
</div>
<p className="text-lg text-type-secondary">{message}</p>
</Flare.Child>
</div>
</Flare.Base>
</div>
);
}