Skip to content
Merged
123 changes: 78 additions & 45 deletions platforms/blabsy/src/components/layout/common-layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Comment thread
Bekiboo marked this conversation as resolved.
Outdated
import { Modal } from '@components/modal/modal';
import { Button } from '@components/ui/button';
import { useAuth } from '@lib/context/auth-context';
Expand All @@ -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);
}, []);
Comment thread
coderabbitai[bot] marked this conversation as resolved.

const handleOutsideClick = () => {
setIsPulsing(true);
setShowHint(true);
setTimeout(() => setIsPulsing(false), 400);
};

if (!user) return <Placeholder />;
if (disclaimerAccepted) return <>{children}</>;

return (
<>
{children}
{!disclaimerAccepted ? (
<Modal
open={!disclaimerAccepted}
closeModal={() => signOut()}
className='max-w-lg mx-auto mt-24'
modalClassName='bg-black backdrop-blur-md p-6 rounded-lg flex flex-col gap-2'
<Modal
open={true}
closeModal={handleOutsideClick}
className='max-w-lg mx-auto mt-24'
modalClassName={`bg-black backdrop-blur-md p-6 rounded-lg flex flex-col gap-2 ${isPulsing ? 'animate-pulse-scale' : ''}`}
>
<style>{`
@keyframes pulse-scale {
0% { transform: scale(1); }
25% { transform: scale(1.01); }
50% { transform: scale(0.99); }
75% { transform: scale(1.005); }
100% { transform: scale(1); }
}
.animate-pulse-scale {
animation: pulse-scale 0.4s ease-in-out;
}
`}</style>
<h1 className='text-xl text-center font-bold'>
Disclaimer from MetaState Foundation
</h1>
<p className='font-bold'>⚠️ Please note:</p>
<p>
Blabsy is a <b>functional prototype</b>, intended to
showcase <b>interoperability</b> and core concepts of
the W3DS ecosystem.
</p>
<p>
<b>It is not a production-grade platform</b> and may
lack full reliability, performance, and security
guarantees.
</p>
<p>
We <b>strongly recommend</b> that you avoid sharing{' '}
<b>sensitive or private content</b>, and kindly ask for
your understanding regarding any bugs, incomplete
features, or unexpected behaviours.
</p>
<p>
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:
</p>
<a
href='mailto:info@metastate.foundation'
className='outline-none'
>
<h1 className='text-xl text-center font-bold'>
Disclaimer from MetaState Foundation
</h1>
<p className='font-bold'>⚠️ Please note:</p>
<p>
Blabsy is a <b>functional prototype</b>, intended to
showcase <b>interoperability</b> and core concepts of
the W3DS ecosystem.
</p>
<p>
<b>It is not a production-grade platform</b> and may
lack full reliability, performance, and security
guarantees.
</p>
<p>
We <b>strongly recommend</b> that you avoid sharing{' '}
<b>sensitive or private content</b>, and kindly ask for
your understanding regarding any bugs, incomplete
features, or unexpected behaviours.
</p>
<p>
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:
</p>
<a
href='mailto:info@metastate.foundation'
className='outline-none'
>
info@metastate.foundation
</a>
info@metastate.foundation
</a>
<div className='relative mt-4'>
{showHint && (
<div className='mb-2 text-xs text-center text-yellow-400 bg-yellow-900/30 px-3 py-2 rounded'>
💡 You must accept the disclaimer to continue. This will only appear once.
</div>
)}
Comment thread
Bekiboo marked this conversation as resolved.
<Button
type='button'
className='mt-4 bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600'
className='w-full bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600'
onClick={() => {
localStorage.setItem(DISCLAIMER_KEY, 'true');
setDisclaimerAccepted(true);
}}
>
I Understand
</Button>
</Modal>
) : (
<></>
)}
</div>
</Modal>
</>
);
}
Expand Down
195 changes: 128 additions & 67 deletions platforms/eVoting/src/app/(app)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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);
}
}, []);
Comment thread
Bekiboo marked this conversation as resolved.

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() }));
};
Expand All @@ -52,77 +77,113 @@ export default function AppLayout({
};
}, [router]);

if (disclaimerAccepted) {
return (
<ProtectedRoute>
<>
<Navigation />
{children}
</>
</ProtectedRoute>
);
}

return (
<ProtectedRoute>
<>
<Navigation />
{children}
{!disclaimerAccepted ? (
<Dialog open>
<DialogContent
className="max-w-lg mx-auto backdrop-blur-md p-6 rounded-lg"
onInteractOutside={() => logout()}
>
<DialogHeader>
<DialogTitle className="text-xl text-center font-bold">
Disclaimer from MetaState Foundation
</DialogTitle>
<DialogDescription asChild>
<div className="flex flex-col gap-2">
<p className="font-bold">⚠️ Please note:</p>
<p>
eVoting is a <b>functional prototype</b>
, intended to showcase{" "}
<b>interoperability</b> and core
concepts of the W3DS ecosystem.
</p>
<p>
<b>
It is not a production-grade
platform
</b>{" "}
and may lack full reliability,
performance, and security guarantees.
</p>
<p>
We <b>strongly recommend</b> that you
avoid sharing{" "}
<b>sensitive or private content</b>, and
kindly ask for your understanding
regarding any bugs, incomplete features,
or unexpected behaviours.
</p>
<p>
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:
</p>
<a
href="mailto:info@metastate.foundation"
className="outline-none"
<Dialog open>
<DialogContent
className={cn(
"max-w-lg mx-auto backdrop-blur-md p-6 rounded-lg",
isPulsing && "animate-pulse-scale"
)}
style={{
animationName: isPulsing ? 'pulse-scale' : 'none'
}}
hideCloseButton={true}
Comment thread
Bekiboo marked this conversation as resolved.
onInteractOutside={handleInteractOutside}
>
<style jsx>{`
@keyframes pulse-scale {
0% { transform: scale(1); }
25% { transform: scale(1.01); }
50% { transform: scale(0.99); }
75% { transform: scale(1.005); }
100% { transform: scale(1); }
}
`}</style>
<DialogHeader>
<DialogTitle className="text-xl text-center font-bold">
Disclaimer from MetaState Foundation
</DialogTitle>
<DialogDescription asChild>
<div className="flex flex-col gap-2">
<p className="font-bold">⚠️ Please note:</p>
<p>
eVoting is a <b>functional prototype</b>
, intended to showcase{" "}
<b>interoperability</b> and core
concepts of the W3DS ecosystem.
</p>
<p>
<b>
It is not a production-grade
platform
</b>{" "}
and may lack full reliability,
performance, and security guarantees.
</p>
<p>
We <b>strongly recommend</b> that you
avoid sharing{" "}
<b>sensitive or private content</b>, and
kindly ask for your understanding
regarding any bugs, incomplete features,
or unexpected behaviours.
</p>
<p>
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:
</p>
<a
href="mailto:info@metastate.foundation"
className="outline-none"
>
info@metastate.foundation
</a>
</div>
</DialogDescription>
</DialogHeader>
<DialogFooter className="flex-col sm:flex-col gap-3">
<TooltipProvider>
<Tooltip open={showHint}>
<TooltipTrigger asChild>
<Button
type="button"
className="w-full"
onClick={() => {
localStorage.setItem(DISCLAIMER_KEY, "true");
setDisclaimerAccepted(true);
}}
>
info@metastate.foundation
</a>
</div>
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button
type="button"
onClick={() => {
setDisclaimerAccepted(true);
}}
>
I Understand
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
) : (
<></>
)}
I Understand
</Button>
</TooltipTrigger>
<TooltipContent side="bottom" className="max-w-xs bg-red-100 text-red-800 border border-red-300">
<p className="text-xs">
You must accept the disclaimer to continue. This will only appear once.
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</DialogFooter>
</DialogContent>
</Dialog>
</>
</ProtectedRoute>
);
Expand Down
Loading