From acdc4b7017f1e6258aad9e45de3504b4918eaea3 Mon Sep 17 00:00:00 2001 From: Bekiboo Date: Wed, 28 Jan 2026 09:17:13 +0300 Subject: [PATCH 1/8] fix: add allowClickOutside prop to Modal for improved interaction --- .../pictique/src/lib/ui/Modal/Modal.svelte | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/platforms/pictique/src/lib/ui/Modal/Modal.svelte b/platforms/pictique/src/lib/ui/Modal/Modal.svelte index 9fea3b835..528964d4e 100644 --- a/platforms/pictique/src/lib/ui/Modal/Modal.svelte +++ b/platforms/pictique/src/lib/ui/Modal/Modal.svelte @@ -4,10 +4,11 @@ interface IModalProps { open: boolean; onclose?: () => void; + allowClickOutside?: boolean; children?: Snippet; } - const { open, onclose, children }: IModalProps = $props(); + const { open, onclose, allowClickOutside, children }: IModalProps = $props(); let modal: HTMLDialogElement | null = $state(null); @@ -19,9 +20,25 @@ event.clientY <= rect.top + rect.height && rect.left <= event.clientX && event.clientX <= rect.left + rect.width; - if (!isInDialog) { + if (!isInDialog && allowClickOutside) { modal.close(); onclose?.(); + } else { + event.stopPropagation(); + // make the modal pulse to indicate it is still open + modal.animate( + [ + { transform: 'scale(1)' }, + { transform: 'scale(1.025)' }, + { transform: 'scale(1)' }, + { transform: 'scale(1.0125)' }, + { transform: 'scale(1)' } + ], + { + duration: 250, + easing: 'ease-in-out' + } + ); } }; From 6a958873e983e7c4ef95ce341ebda21007983651 Mon Sep 17 00:00:00 2001 From: Bekiboo Date: Wed, 28 Jan 2026 11:06:43 +0300 Subject: [PATCH 2/8] fix: enhance DisclaimerModal with tooltip and animation effects --- .../src/components/disclaimer-modal.tsx | 190 +++++++++++------- .../src/components/ui/dialog.tsx | 16 +- 2 files changed, 129 insertions(+), 77 deletions(-) diff --git a/platforms/group-charter-manager/src/components/disclaimer-modal.tsx b/platforms/group-charter-manager/src/components/disclaimer-modal.tsx index 18fa3aaa5..f806f1b04 100644 --- a/platforms/group-charter-manager/src/components/disclaimer-modal.tsx +++ b/platforms/group-charter-manager/src/components/disclaimer-modal.tsx @@ -9,80 +9,128 @@ import { DialogDescription, DialogFooter, } from "@/components/ui/dialog"; -import { useState } from "react"; -import { useAuth } from "@/components/auth/auth-provider"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { useState, useEffect } from "react"; +import { cn } from "@/lib/utils"; + +const DISCLAIMER_KEY = "group-charter-disclaimer-accepted"; export default function DisclaimerModal() { - const { logout } = useAuth(); - const [disclaimerAccepted, setDisclaimerAccepted] = useState(false); + const [disclaimerAccepted, setDisclaimerAccepted] = useState(true); // Start as true to prevent flash + const [showHint, setShowHint] = useState(false); + const [isPulsing, setIsPulsing] = useState(false); + + useEffect(() => { + // Check if disclaimer was previously accepted + const accepted = localStorage.getItem(DISCLAIMER_KEY) === "true"; + setDisclaimerAccepted(accepted); + }, []); + + const handleInteractOutside = (e: Event) => { + e.preventDefault(); + setIsPulsing(true); + setShowHint(true); + setTimeout(() => setIsPulsing(false), 400); + }; + + if (disclaimerAccepted) return null; + return ( - <> - {!disclaimerAccepted ? ( - - logout()} - > - - - Disclaimer from MetaState Foundation - - -
-

⚠️ Please note:

-

- Group Charter is a{" "} - functional prototype, intended to - showcase interoperability and - core concepts of the W3DS ecosystem. -

-

- - It is not a production-grade - platform - {" "} - and may lack full reliability, - performance, and security guarantees. -

-

- We strongly recommend that you - avoid sharing{" "} - sensitive or private content, and - kindly ask for your understanding - regarding any bugs, incomplete features, - or unexpected behaviours. -

-

- The app is still in development, so we - kindly ask for your understanding - regarding any potential issues. If you - experience issues or have feedback, feel - free to contact us at: -

- - info@metastate.foundation - -
-
-
- - - -
-
- ) : ( - <> - )} - + info@metastate.foundation + + + + + + + + + + + +

+ You must accept the disclaimer to continue. This will only appear once. +

+
+
+
+
+ + ); } diff --git a/platforms/group-charter-manager/src/components/ui/dialog.tsx b/platforms/group-charter-manager/src/components/ui/dialog.tsx index f38593bdc..62dbb94aa 100644 --- a/platforms/group-charter-manager/src/components/ui/dialog.tsx +++ b/platforms/group-charter-manager/src/components/ui/dialog.tsx @@ -31,8 +31,10 @@ DialogOverlay.displayName = DialogPrimitive.Overlay.displayName const DialogContent = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, children, ...props }, ref) => ( + React.ComponentPropsWithoutRef & { + hideCloseButton?: boolean + } +>(({ className, children, hideCloseButton, ...props }, ref) => ( {children} - - - Close - + {!hideCloseButton && ( + + + Close + + )} )) From 7e2cd816948d1c330510ed93251b06e1fe47259f Mon Sep 17 00:00:00 2001 From: Bekiboo Date: Wed, 28 Jan 2026 11:07:26 +0300 Subject: [PATCH 3/8] fix: update ProtectedLayout to include disclaimer modal with enhanced styling and interaction --- .../src/components/layout/common-layout.tsx | 123 +++++++++++------- 1 file changed, 78 insertions(+), 45 deletions(-) diff --git a/platforms/blabsy/src/components/layout/common-layout.tsx b/platforms/blabsy/src/components/layout/common-layout.tsx index 41dcdc101..09d0c27bc 100644 --- a/platforms/blabsy/src/components/layout/common-layout.tsx +++ b/platforms/blabsy/src/components/layout/common-layout.tsx @@ -2,7 +2,7 @@ import { useRequireAuth } from '@lib/hooks/useRequireAuth'; import { Aside } from '@components/aside/aside'; import { Suggestions } from '@components/aside/suggestions'; import { Placeholder } from '@components/common/placeholder'; -import { type ReactNode, useState } from 'react'; +import { type ReactNode, useState, useEffect } from 'react'; import { Modal } from '@components/modal/modal'; import { Button } from '@components/ui/button'; import { useAuth } from '@lib/context/auth-context'; @@ -11,68 +11,101 @@ export type LayoutProps = { children: ReactNode; }; +const DISCLAIMER_KEY = 'blabsy-disclaimer-accepted'; + export function ProtectedLayout({ children }: LayoutProps): JSX.Element { const user = useRequireAuth(); const { signOut } = useAuth(); - const [disclaimerAccepted, setDisclaimerAccepted] = useState(false); + const [disclaimerAccepted, setDisclaimerAccepted] = useState(true); + const [showHint, setShowHint] = useState(false); + const [isPulsing, setIsPulsing] = useState(false); + + useEffect(() => { + const accepted = localStorage.getItem(DISCLAIMER_KEY) === 'true'; + setDisclaimerAccepted(accepted); + }, []); + + const handleOutsideClick = () => { + setIsPulsing(true); + setShowHint(true); + setTimeout(() => setIsPulsing(false), 400); + }; + if (!user) return ; + if (disclaimerAccepted) return <>{children}; return ( <> {children} - {!disclaimerAccepted ? ( - signOut()} - className='max-w-lg mx-auto mt-24' - modalClassName='bg-black backdrop-blur-md p-6 rounded-lg flex flex-col gap-2' + + +

+ Disclaimer from MetaState Foundation +

+

⚠️ Please note:

+

+ Blabsy is a functional prototype, intended to + showcase interoperability and core concepts of + the W3DS ecosystem. +

+

+ It is not a production-grade platform and may + lack full reliability, performance, and security + guarantees. +

+

+ We strongly recommend that you avoid sharing{' '} + sensitive or private content, and kindly ask for + your understanding regarding any bugs, incomplete + features, or unexpected behaviours. +

+

+ The app is still in development, so we kindly ask for + your understanding regarding any potential issues. If + you experience issues or have feedback, feel free to + contact us at: +

+ -

- Disclaimer from MetaState Foundation -

-

⚠️ Please note:

-

- Blabsy is a functional prototype, intended to - showcase interoperability and core concepts of - the W3DS ecosystem. -

-

- It is not a production-grade platform and may - lack full reliability, performance, and security - guarantees. -

-

- We strongly recommend that you avoid sharing{' '} - sensitive or private content, and kindly ask for - your understanding regarding any bugs, incomplete - features, or unexpected behaviours. -

-

- The app is still in development, so we kindly ask for - your understanding regarding any potential issues. If - you experience issues or have feedback, feel free to - contact us at: -

-
- info@metastate.foundation - + info@metastate.foundation + +
+ {showHint && ( +
+ 💡 You must accept the disclaimer to continue. This will only appear once. +
+ )} - - ) : ( - <> - )} +
+
); } From fd12631036f1201c6e749ee24a4866cfe0359f84 Mon Sep 17 00:00:00 2001 From: Bekiboo Date: Wed, 28 Jan 2026 11:15:49 +0300 Subject: [PATCH 4/8] fix: enhance disclaimer modal with pulsing animation and tooltip for improved user interaction --- platforms/eVoting/src/app/(app)/layout.tsx | 195 ++++++++++++------ .../eVoting/src/components/ui/dialog.tsx | 12 +- 2 files changed, 138 insertions(+), 69 deletions(-) diff --git a/platforms/eVoting/src/app/(app)/layout.tsx b/platforms/eVoting/src/app/(app)/layout.tsx index eaaf9d047..a3ffb9f87 100644 --- a/platforms/eVoting/src/app/(app)/layout.tsx +++ b/platforms/eVoting/src/app/(app)/layout.tsx @@ -12,7 +12,16 @@ import { DialogDescription, DialogFooter, } from "@/components/ui/dialog"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; import { ProtectedRoute } from "@/components/auth/protected-route"; +import { cn } from "@/lib/utils"; + +const DISCLAIMER_KEY = "evoting-disclaimer-accepted"; // Deeplink handling for reveal functionality declare global { @@ -28,17 +37,33 @@ export default function AppLayout({ }>) { const { logout } = useAuth(); const [disclaimerAccepted, setDisclaimerAccepted] = useState(false); + const [isPulsing, setIsPulsing] = useState(false); + const [showHint, setShowHint] = useState(false); const router = useRouter(); + useEffect(() => { + const accepted = localStorage.getItem(DISCLAIMER_KEY) === "true"; + if (accepted) { + setDisclaimerAccepted(true); + } + }, []); + + const handleInteractOutside = (e: Event) => { + e.preventDefault(); + setIsPulsing(true); + setShowHint(true); + setTimeout(() => setIsPulsing(false), 400); + }; + // Handle deeplink reveal requests useEffect(() => { const handleDeepLinkReveal = (event: CustomEvent<{ pollId: string }>) => { console.log("🔍 Deep link reveal request received:", event.detail); const { pollId } = event.detail; - + // Navigate to the poll page to show reveal interface router.push(`/${pollId}`); - + // Store the reveal request in sessionStorage for the poll page to pick up sessionStorage.setItem("revealRequest", JSON.stringify({ pollId, timestamp: Date.now() })); }; @@ -52,77 +77,113 @@ export default function AppLayout({ }; }, [router]); + if (disclaimerAccepted) { + return ( + + <> + + {children} + + + ); + } + return ( <> {children} - {!disclaimerAccepted ? ( - - logout()} - > - - - Disclaimer from MetaState Foundation - - -
-

⚠️ Please note:

-

- eVoting is a functional prototype - , intended to showcase{" "} - interoperability and core - concepts of the W3DS ecosystem. -

-

- - It is not a production-grade - platform - {" "} - and may lack full reliability, - performance, and security guarantees. -

-

- We strongly recommend that you - avoid sharing{" "} - sensitive or private content, and - kindly ask for your understanding - regarding any bugs, incomplete features, - or unexpected behaviours. -

-

- The app is still in development, so we - kindly ask for your understanding - regarding any potential issues. If you - experience issues or have feedback, feel - free to contact us at: -

- + + + + + Disclaimer from MetaState Foundation + + + + + + + + + +
-
-
- - - -
-
- ) : ( - <> - )} + I Understand + + + +

+ You must accept the disclaimer to continue. This will only appear once. +

+
+ + + + +
); diff --git a/platforms/eVoting/src/components/ui/dialog.tsx b/platforms/eVoting/src/components/ui/dialog.tsx index 248380ea1..6382ca292 100644 --- a/platforms/eVoting/src/components/ui/dialog.tsx +++ b/platforms/eVoting/src/components/ui/dialog.tsx @@ -31,8 +31,10 @@ DialogOverlay.displayName = DialogPrimitive.Overlay.displayName; const DialogContent = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, children, ...props }, ref) => ( + React.ComponentPropsWithoutRef & { + hideCloseButton?: boolean + } +>(({ className, children, hideCloseButton, ...props }, ref) => ( {children} + {!hideCloseButton && ( + + + Close + + )} )); From ea5e8b78a8cb9a89810204bcca4a7b6314250765 Mon Sep 17 00:00:00 2001 From: Bekiboo Date: Wed, 28 Jan 2026 11:31:21 +0300 Subject: [PATCH 5/8] fix: improve disclaimer handling with localStorage check and user hint --- .../pictique/src/lib/ui/Modal/Modal.svelte | 3 ++- .../src/routes/(protected)/+layout.svelte | 27 +++++++++++++++---- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/platforms/pictique/src/lib/ui/Modal/Modal.svelte b/platforms/pictique/src/lib/ui/Modal/Modal.svelte index 528964d4e..b22ac495c 100644 --- a/platforms/pictique/src/lib/ui/Modal/Modal.svelte +++ b/platforms/pictique/src/lib/ui/Modal/Modal.svelte @@ -23,7 +23,7 @@ if (!isInDialog && allowClickOutside) { modal.close(); onclose?.(); - } else { + } else if (!isInDialog) { event.stopPropagation(); // make the modal pulse to indicate it is still open modal.animate( @@ -39,6 +39,7 @@ easing: 'ease-in-out' } ); + onclose?.(); } }; diff --git a/platforms/pictique/src/routes/(protected)/+layout.svelte b/platforms/pictique/src/routes/(protected)/+layout.svelte index 73a5043ff..161260259 100644 --- a/platforms/pictique/src/routes/(protected)/+layout.svelte +++ b/platforms/pictique/src/routes/(protected)/+layout.svelte @@ -18,6 +18,9 @@ let profile = $state(null); let confirmedDisclaimer = $state(false); + let showHint = $state(false); + + const DISCLAIMER_KEY = 'pictique-disclaimer-accepted'; async function fetchProfile() { ownerId = getAuthId(); @@ -37,7 +40,14 @@ } } - onMount(fetchProfile); + onMount(() => { + fetchProfile(); + const accepted = localStorage.getItem(DISCLAIMER_KEY) === 'true'; + if (accepted) { + confirmedDisclaimer = true; + closeDisclaimerModal(); + } + });
@@ -59,9 +69,7 @@ open={$isDisclaimerModalOpen} onclose={() => { if (!confirmedDisclaimer) { - removeAuthToken(); - removeAuthId(); - goto('/auth'); + showHint = true; } }} > @@ -91,11 +99,20 @@ + {#if showHint} +

+ 💡 You must accept the disclaimer to continue. This will only appear once. +

+ {/if} From 43162b44f2ad8c172d59e1fe0984394d4f2822df Mon Sep 17 00:00:00 2001 From: Bekiboo Date: Wed, 28 Jan 2026 12:04:02 +0300 Subject: [PATCH 6/8] fix: update Modal to use onClickOutside prop for improved interaction and animation --- .../pictique/src/lib/ui/Modal/Modal.svelte | 31 ++++++------------- .../src/routes/(protected)/+layout.svelte | 16 ++++++++-- 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/platforms/pictique/src/lib/ui/Modal/Modal.svelte b/platforms/pictique/src/lib/ui/Modal/Modal.svelte index b22ac495c..00e66339f 100644 --- a/platforms/pictique/src/lib/ui/Modal/Modal.svelte +++ b/platforms/pictique/src/lib/ui/Modal/Modal.svelte @@ -4,11 +4,11 @@ interface IModalProps { open: boolean; onclose?: () => void; - allowClickOutside?: boolean; + onClickOutside?: (modal: HTMLDialogElement) => void; children?: Snippet; } - const { open, onclose, allowClickOutside, children }: IModalProps = $props(); + const { open, onclose, onClickOutside, children }: IModalProps = $props(); let modal: HTMLDialogElement | null = $state(null); @@ -20,26 +20,15 @@ event.clientY <= rect.top + rect.height && rect.left <= event.clientX && event.clientX <= rect.left + rect.width; - if (!isInDialog && allowClickOutside) { - modal.close(); - onclose?.(); - } else if (!isInDialog) { + if (!isInDialog) { event.stopPropagation(); - // make the modal pulse to indicate it is still open - modal.animate( - [ - { transform: 'scale(1)' }, - { transform: 'scale(1.025)' }, - { transform: 'scale(1)' }, - { transform: 'scale(1.0125)' }, - { transform: 'scale(1)' } - ], - { - duration: 250, - easing: 'ease-in-out' - } - ); - onclose?.(); + if (onClickOutside) { + onClickOutside(modal); + } else { + // Default behavior: close the modal + modal.close(); + onclose?.(); + } } }; diff --git a/platforms/pictique/src/routes/(protected)/+layout.svelte b/platforms/pictique/src/routes/(protected)/+layout.svelte index 161260259..5c21bb191 100644 --- a/platforms/pictique/src/routes/(protected)/+layout.svelte +++ b/platforms/pictique/src/routes/(protected)/+layout.svelte @@ -8,7 +8,6 @@ import type { userProfile } from '$lib/types'; import { Button, Modal } from '$lib/ui'; import { apiClient, getAuthId, getAuthToken } from '$lib/utils'; - import { removeAuthId, removeAuthToken } from '$lib/utils'; import type { AxiosError } from 'axios'; import { onMount } from 'svelte'; @@ -67,9 +66,22 @@ { + onClickOutside={(modal) => { if (!confirmedDisclaimer) { showHint = true; + modal.animate( + [ + { transform: 'scale(1)' }, + { transform: 'scale(1.025)' }, + { transform: 'scale(1)' }, + { transform: 'scale(1.0125)' }, + { transform: 'scale(1)' } + ], + { + duration: 250, + easing: 'ease-in-out' + } + ); } }} > From 728d13870f71023fd6b0628ba9f6708f16328fb1 Mon Sep 17 00:00:00 2001 From: Bekiboo Date: Wed, 28 Jan 2026 12:13:48 +0300 Subject: [PATCH 7/8] fix: implement safe localStorage access methods for disclaimer handling across components --- .../src/components/layout/common-layout.tsx | 21 +++++++++++++-- platforms/eVoting/src/app/(app)/layout.tsx | 26 ++++++++++++++----- .../src/components/disclaimer-modal.tsx | 21 +++++++++++++-- .../src/routes/(protected)/+layout.svelte | 21 +++++++++++++-- 4 files changed, 77 insertions(+), 12 deletions(-) diff --git a/platforms/blabsy/src/components/layout/common-layout.tsx b/platforms/blabsy/src/components/layout/common-layout.tsx index 09d0c27bc..b3ff406b9 100644 --- a/platforms/blabsy/src/components/layout/common-layout.tsx +++ b/platforms/blabsy/src/components/layout/common-layout.tsx @@ -13,6 +13,23 @@ export type LayoutProps = { const DISCLAIMER_KEY = 'blabsy-disclaimer-accepted'; +// Safe localStorage access for restricted environments +const safeGetItem = (key: string): string | null => { + try { + return localStorage.getItem(key); + } catch { + return null; + } +}; + +const safeSetItem = (key: string, value: string): void => { + try { + localStorage.setItem(key, value); + } catch { + // Silently fail in restricted environments + } +}; + export function ProtectedLayout({ children }: LayoutProps): JSX.Element { const user = useRequireAuth(); const { signOut } = useAuth(); @@ -22,7 +39,7 @@ export function ProtectedLayout({ children }: LayoutProps): JSX.Element { const [isPulsing, setIsPulsing] = useState(false); useEffect(() => { - const accepted = localStorage.getItem(DISCLAIMER_KEY) === 'true'; + const accepted = safeGetItem(DISCLAIMER_KEY) === 'true'; setDisclaimerAccepted(accepted); }, []); @@ -98,7 +115,7 @@ export function ProtectedLayout({ children }: LayoutProps): JSX.Element { type='button' className='w-full bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600' onClick={() => { - localStorage.setItem(DISCLAIMER_KEY, 'true'); + safeSetItem(DISCLAIMER_KEY, 'true'); setDisclaimerAccepted(true); }} > diff --git a/platforms/eVoting/src/app/(app)/layout.tsx b/platforms/eVoting/src/app/(app)/layout.tsx index a3ffb9f87..6ac642d6d 100644 --- a/platforms/eVoting/src/app/(app)/layout.tsx +++ b/platforms/eVoting/src/app/(app)/layout.tsx @@ -1,6 +1,5 @@ "use client"; import Navigation from "@/components/navigation"; -import { useAuth } from "@/lib/auth-context"; import { useRouter } from "next/navigation"; import { useEffect, useState } from "react"; import { Button } from "@/components/ui/button"; @@ -23,6 +22,23 @@ import { cn } from "@/lib/utils"; const DISCLAIMER_KEY = "evoting-disclaimer-accepted"; +// Safe localStorage access for restricted environments +const safeGetItem = (key: string): string | null => { + try { + return localStorage.getItem(key); + } catch { + return null; + } +}; + +const safeSetItem = (key: string, value: string): void => { + try { + localStorage.setItem(key, value); + } catch { + // Silently fail in restricted environments + } +}; + // Deeplink handling for reveal functionality declare global { interface WindowEventMap { @@ -35,14 +51,13 @@ export default function AppLayout({ }: Readonly<{ children: React.ReactNode; }>) { - const { logout } = useAuth(); const [disclaimerAccepted, setDisclaimerAccepted] = useState(false); const [isPulsing, setIsPulsing] = useState(false); const [showHint, setShowHint] = useState(false); const router = useRouter(); useEffect(() => { - const accepted = localStorage.getItem(DISCLAIMER_KEY) === "true"; + const accepted = safeGetItem(DISCLAIMER_KEY) === "true"; if (accepted) { setDisclaimerAccepted(true); } @@ -96,8 +111,7 @@ export default function AppLayout({ { - localStorage.setItem(DISCLAIMER_KEY, "true"); + safeSetItem(DISCLAIMER_KEY, "true"); setDisclaimerAccepted(true); }} > diff --git a/platforms/group-charter-manager/src/components/disclaimer-modal.tsx b/platforms/group-charter-manager/src/components/disclaimer-modal.tsx index f806f1b04..da337869a 100644 --- a/platforms/group-charter-manager/src/components/disclaimer-modal.tsx +++ b/platforms/group-charter-manager/src/components/disclaimer-modal.tsx @@ -20,6 +20,23 @@ import { cn } from "@/lib/utils"; const DISCLAIMER_KEY = "group-charter-disclaimer-accepted"; +// Safe localStorage access for restricted environments +const safeGetItem = (key: string): string | null => { + try { + return localStorage.getItem(key); + } catch { + return null; + } +}; + +const safeSetItem = (key: string, value: string): void => { + try { + localStorage.setItem(key, value); + } catch { + // Silently fail in restricted environments + } +}; + export default function DisclaimerModal() { const [disclaimerAccepted, setDisclaimerAccepted] = useState(true); // Start as true to prevent flash const [showHint, setShowHint] = useState(false); @@ -27,7 +44,7 @@ export default function DisclaimerModal() { useEffect(() => { // Check if disclaimer was previously accepted - const accepted = localStorage.getItem(DISCLAIMER_KEY) === "true"; + const accepted = safeGetItem(DISCLAIMER_KEY) === "true"; setDisclaimerAccepted(accepted); }, []); @@ -115,7 +132,7 @@ export default function DisclaimerModal() { type="button" className="w-full" onClick={() => { - localStorage.setItem(DISCLAIMER_KEY, "true"); + safeSetItem(DISCLAIMER_KEY, "true"); setDisclaimerAccepted(true); }} > diff --git a/platforms/pictique/src/routes/(protected)/+layout.svelte b/platforms/pictique/src/routes/(protected)/+layout.svelte index 5c21bb191..ca6cfc5bd 100644 --- a/platforms/pictique/src/routes/(protected)/+layout.svelte +++ b/platforms/pictique/src/routes/(protected)/+layout.svelte @@ -21,6 +21,23 @@ const DISCLAIMER_KEY = 'pictique-disclaimer-accepted'; + // Safe localStorage access for restricted environments + const safeGetItem = (key: string): string | null => { + try { + return localStorage.getItem(key); + } catch { + return null; + } + }; + + const safeSetItem = (key: string, value: string): void => { + try { + localStorage.setItem(key, value); + } catch { + // Silently fail in restricted environments + } + }; + async function fetchProfile() { ownerId = getAuthId(); try { @@ -41,7 +58,7 @@ onMount(() => { fetchProfile(); - const accepted = localStorage.getItem(DISCLAIMER_KEY) === 'true'; + const accepted = safeGetItem(DISCLAIMER_KEY) === 'true'; if (accepted) { confirmedDisclaimer = true; closeDisclaimerModal(); @@ -114,7 +131,7 @@ class="mt-2 w-full" data-disclaimer-button callback={() => { - localStorage.setItem(DISCLAIMER_KEY, 'true'); + safeSetItem(DISCLAIMER_KEY, 'true'); closeDisclaimerModal(); confirmedDisclaimer = true; }}>I Understand Date: Wed, 28 Jan 2026 12:20:47 +0300 Subject: [PATCH 8/8] fix: improve disclaimer handling with enhanced localStorage access and user interaction --- .../src/components/layout/common-layout.tsx | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/platforms/blabsy/src/components/layout/common-layout.tsx b/platforms/blabsy/src/components/layout/common-layout.tsx index b3ff406b9..f345f986a 100644 --- a/platforms/blabsy/src/components/layout/common-layout.tsx +++ b/platforms/blabsy/src/components/layout/common-layout.tsx @@ -2,10 +2,9 @@ import { useRequireAuth } from '@lib/hooks/useRequireAuth'; import { Aside } from '@components/aside/aside'; import { Suggestions } from '@components/aside/suggestions'; import { Placeholder } from '@components/common/placeholder'; -import { type ReactNode, useState, useEffect } from 'react'; +import { type ReactNode, useState, useEffect, useRef } from 'react'; import { Modal } from '@components/modal/modal'; import { Button } from '@components/ui/button'; -import { useAuth } from '@lib/context/auth-context'; export type LayoutProps = { children: ReactNode; @@ -32,24 +31,32 @@ const safeSetItem = (key: string, value: string): void => { export function ProtectedLayout({ children }: LayoutProps): JSX.Element { const user = useRequireAuth(); - const { signOut } = useAuth(); - const [disclaimerAccepted, setDisclaimerAccepted] = useState(true); + const [disclaimerAccepted, setDisclaimerAccepted] = useState(false); + const [disclaimerChecked, setDisclaimerChecked] = useState(false); const [showHint, setShowHint] = useState(false); const [isPulsing, setIsPulsing] = useState(false); useEffect(() => { - const accepted = safeGetItem(DISCLAIMER_KEY) === 'true'; + let accepted = false; + try { + accepted = localStorage.getItem(DISCLAIMER_KEY) === 'true'; + } catch { + // Storage may be unavailable; fall back to session-only acceptance. + } setDisclaimerAccepted(accepted); + setDisclaimerChecked(true); }, []); + const pulseTimeoutRef = useRef | null>(null); const handleOutsideClick = () => { setIsPulsing(true); setShowHint(true); - setTimeout(() => setIsPulsing(false), 400); + if (pulseTimeoutRef.current) clearTimeout(pulseTimeoutRef.current); + pulseTimeoutRef.current = setTimeout(() => setIsPulsing(false), 400); }; - if (!user) return ; + if (!disclaimerChecked) return <>; if (disclaimerAccepted) return <>{children}; return ( @@ -101,7 +108,7 @@ export function ProtectedLayout({ children }: LayoutProps): JSX.Element {

info@metastate.foundation @@ -114,8 +121,13 @@ export function ProtectedLayout({ children }: LayoutProps): JSX.Element {