-
Notifications
You must be signed in to change notification settings - Fork 4
Feat/mobile responsiveness #292
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,29 +1,30 @@ | ||
"use client"; | ||
import { useState, useEffect } from "react"; | ||
import { Card, CardHeader } from "@/components/ui/card"; | ||
import { useRouter } from "next/navigation"; | ||
import QRCode from "qrcode.react"; | ||
import { useAuth } from "@/lib/auth-context"; | ||
import { setAuthToken, setAuthId } from "@/lib/authUtils"; | ||
import { isMobileDevice, getDeepLinkUrl } from "@/lib/utils/mobile-detection"; | ||
|
||
export default function LoginPage() { | ||
const router = useRouter(); | ||
const { login } = useAuth(); | ||
const [qrData, setQrData] = useState<string | null>(null); | ||
const [sessionId, setSessionId] = useState<string | null>(null); | ||
const [error, setError] = useState<string | null>(null); | ||
const [isLoading, setIsLoading] = useState(true); | ||
const [isMobile, setIsMobile] = useState(false); | ||
|
||
useEffect(() => { | ||
setIsMobile(isMobileDevice()); | ||
}, []); | ||
|
||
useEffect(() => { | ||
const fetchQRCode = async () => { | ||
try { | ||
const response = await fetch(`${process.env.NEXT_PUBLIC_EVOTING_BASE_URL}/api/auth/offer`, { | ||
method: "GET", | ||
headers: { | ||
"Content-Type": "application/json", | ||
}, | ||
}); | ||
const response = await fetch( | ||
`${process.env.NEXT_PUBLIC_EVOTING_BASE_URL}/api/auth/offer`, | ||
{ method: "GET", headers: { "Content-Type": "application/json" } } | ||
); | ||
|
||
if (!response.ok) { | ||
throw new Error("Failed to fetch QR code"); | ||
|
@@ -33,7 +34,7 @@ export default function LoginPage() { | |
setQrData(data.offer); | ||
setSessionId(data.sessionId); | ||
setIsLoading(false); | ||
} catch (err) { | ||
} catch { | ||
setError("Failed to load QR code"); | ||
setIsLoading(false); | ||
} | ||
|
@@ -54,59 +55,69 @@ export default function LoginPage() { | |
if (data.token && data.user) { | ||
setAuthToken(data.token); | ||
setAuthId(data.user.id); | ||
// Reload to trigger auth initialization | ||
window.location.href = '/'; | ||
window.location.href = "/"; | ||
} | ||
}; | ||
|
||
eventSource.onerror = () => { | ||
eventSource.close(); | ||
}; | ||
|
||
return () => { | ||
eventSource.close(); | ||
}; | ||
return () => eventSource.close(); | ||
}, [sessionId, login]); | ||
|
||
return ( | ||
<div className="modal-container flex flex-col items-center justify-center gap-4 safe-area-top safe-area-bottom min-h-screen px-4 pb-safe"> | ||
<div className="flex flex-col items-center justify-center gap-4 min-h-screen px-4 pb-safe"> | ||
{/* Logo + Tagline */} | ||
<div className="flex flex-col items-center text-center gap-4"> | ||
<div className="flex items-center gap-2 text-2xl font-bold"> | ||
<img src="/Logo.png" alt="eVoting Logo" className="h-12" /> | ||
eVoting | ||
</div> | ||
<p className="text-2xl">Secure voting in the W3DS</p> | ||
<p className="text-lg sm:text-2xl">Secure voting in the W3DS</p> | ||
</div> | ||
<Card className="flex flex-col items-center gap-1 w-full max-w-md p-4 pt-2 mx-4"> | ||
<CardHeader className="text-foreground text-2xl font-black"> | ||
|
||
{/* Main Card */} | ||
<Card className="flex flex-col items-center gap-4 w-full max-w-md p-4 pt-2 mx-4"> | ||
<CardHeader className="text-foreground text-xl sm:text-2xl font-black text-center"> | ||
Welcome to eVoting | ||
</CardHeader> | ||
<div className="flex flex-col gap-4 text-muted-foreground items-center"> | ||
<div className="flex justify-center items-center text-xl space-x-1 whitespace-nowrap"> | ||
<span>Scan the QR using your</span> | ||
<span className="font-bold underline">eID App</span> | ||
<span>to login</span> | ||
|
||
<div className="flex flex-col gap-4 text-muted-foreground items-center text-center"> | ||
{/* Dynamic heading text */} | ||
<div className="text-lg sm:text-xl space-x-1"> | ||
{isMobile ? ( | ||
<> | ||
<span>Click the button below using your</span> | ||
<span className="font-bold underline">eID App</span> | ||
<span>to login</span> | ||
</> | ||
) : ( | ||
<> | ||
<span>Scan the QR using your</span> | ||
<span className="font-bold underline">eID App</span> | ||
<span>to login</span> | ||
</> | ||
)} | ||
</div> | ||
{error && ( | ||
<div className="w-full text-red-500 text-center"> | ||
{error} | ||
</div> | ||
)} | ||
|
||
{error && <div className="w-full text-red-500">{error}</div>} | ||
|
||
{isLoading ? ( | ||
<div className="w-48 h-48 bg-gray-100 rounded-lg flex items-center justify-center"> | ||
<div className="text-gray-500">Loading QR Code...</div> | ||
</div> | ||
) : qrData ? ( | ||
<> | ||
{isMobileDevice() ? ( | ||
{isMobile ? ( | ||
<div className="flex flex-col gap-4 items-center"> | ||
<a | ||
href={getDeepLinkUrl(qrData)} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainVerify getDeepLinkUrl implementation. The 🌐 Web query:
💡 Result: Short answer: there isn’t one universal URL format — mobile wallets use either (A) an app-specific custom URI scheme or (B) an HTTPS universal link that the wallet claims — and the common pattern is:
Where the embedded auth/connection URI (e.g. a WalletConnect "wc:..." URI or an OAuth redirect) must be percent-encoded. Wallets (and dapps) commonly use that pattern for WalletConnect-style session/auth requests; iOS prefers claimed HTTPS (universal) links for better UX/security. (alpha-docs.walletconnect.com, reddit.com) Examples
Best practices
If you tell me which wallet/protocol you’re targeting (MetaMask, WalletConnect v1/v2, Phantom, Rainbow, OAuth, Solana, Ethereum EIP‑681, etc.), I can give an exact example URL formatted for that wallet and note any encoding/redirect parameters required. Citations:
Implement proper deep-link formatting in getDeepLinkUrl The helper currently returns the raw
Action items:
🤖 Prompt for AI Agents
|
||
className="px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors text-center" | ||
> | ||
Login with eID Wallet | ||
</a> | ||
<div className="text-xs text-gray-500 text-center max-w-xs"> | ||
<div className="text-xs text-gray-500 max-w-xs"> | ||
Click the button to open your eID wallet app | ||
</div> | ||
</div> | ||
|
@@ -126,21 +137,26 @@ export default function LoginPage() { | |
<div className="text-gray-500">QR Code not available</div> | ||
</div> | ||
)} | ||
<span className="flex flex-col gap-2 items-center"> | ||
|
||
{/* Expiry Note */} | ||
<div> | ||
<p className="font-bold text-md"> | ||
The code is only valid for 60 seconds | ||
</p> | ||
<p>Please refresh the page if it expires</p> | ||
</span> | ||
<div className="bg-muted-foreground/20 p-4 rounded-md text-center"> | ||
You are entering eVoting - a voting platform built on | ||
the Web 3.0 Data Space (WDS) architecture. This system | ||
</div> | ||
|
||
{/* Info Box */} | ||
<div className="bg-muted-foreground/20 p-4 rounded-md"> | ||
You are entering eVoting — a voting platform built on | ||
the Web 3.0 Data Space (W3DS) architecture. This system | ||
is designed around the principle of data-platform | ||
separation, where all your personal content is stored in | ||
your own sovereign eVault, not on centralised servers. | ||
</div> | ||
</div> | ||
</Card> | ||
|
||
<img src="/W3DS.svg" alt="w3ds Logo" className="max-h-8" /> | ||
</div> | ||
); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,11 +5,20 @@ | |
import { Qr } from '$lib/ui'; | ||
import { apiClient, setAuthId, setAuthToken } from '$lib/utils'; | ||
import { onMount } from 'svelte'; | ||
import { onDestroy } from 'svelte'; | ||
import { qrcode } from 'svelte-qrcode-action'; | ||
let qrData: string; | ||
let isMobile = false; | ||
function checkMobile() { | ||
isMobile = window.innerWidth <= 640; // Tailwind's `sm` breakpoint | ||
} | ||
onMount(async () => { | ||
checkMobile(); | ||
window.addEventListener('resize', checkMobile); | ||
const { data } = await apiClient.get('/api/auth/offer'); | ||
qrData = data.uri; | ||
|
@@ -32,60 +41,78 @@ | |
} | ||
watchEventStream(new URL(qrData).searchParams.get('session') as string); | ||
onDestroy(() => { | ||
window.removeEventListener('resize', checkMobile); | ||
}); | ||
Comment on lines
+45
to
+47
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Incorrect placement of onDestroy call. The Move the watchEventStream(new URL(qrData).searchParams.get('session') as string);
-
- onDestroy(() => {
- window.removeEventListener('resize', checkMobile);
- });
});
+
+onDestroy(() => {
+ window.removeEventListener('resize', checkMobile);
+});
</script> 🤖 Prompt for AI Agents
|
||
}); | ||
</script> | ||
|
||
<div | ||
class="align-center flex h-full w-full flex-col content-center items-center | ||
justify-center" | ||
> | ||
<div class="mb-5 flex flex-col items-center gap-2"> | ||
<div class="flex h-full w-full flex-col items-center justify-center p-4"> | ||
<div class="mb-5 flex flex-col items-center gap-2 text-center"> | ||
<img src="/images/Logo.svg" alt="logo" class="w-30" /> | ||
<p>Connect Socially in the Metastate</p> | ||
</div> | ||
|
||
<div | ||
class="h-max-[600px] w-max-[400px] mb-5 flex flex-col items-center gap-5 rounded-xl bg-[#F476481A] p-5" | ||
class="mb-5 flex w-full max-w-[400px] flex-col items-center gap-5 rounded-xl bg-[#F476481A] p-5" | ||
> | ||
<h2>Scan the QR code using your <b><u>eID App</u></b> to login</h2> | ||
{#if qrData} | ||
<article | ||
class="overflow-hidden rounded-2xl" | ||
use:qrcode={{ | ||
data: qrData, | ||
width: 250, | ||
height: 250, | ||
margin: 12, | ||
type: 'canvas', | ||
dotsOptions: { | ||
type: 'rounded', | ||
color: '#fff' | ||
}, | ||
backgroundOptions: { | ||
gradient: { | ||
type: 'linear', | ||
rotation: 50, | ||
colorStops: [ | ||
{ offset: 0, color: '#4D44EF' }, | ||
{ offset: 0.65, color: '#F35B5B' }, | ||
{ offset: 1, color: '#F7A428' } | ||
] | ||
{#if isMobile} | ||
<h2 class="text-center"> | ||
Click the button below to login using your <b><u>eID App</u></b> | ||
</h2> | ||
<a | ||
href={qrData} | ||
class="w-full rounded-lg bg-[#4D44EF] px-4 py-3 text-center font-semibold text-white transition hover:opacity-90" | ||
> | ||
Login with eID Wallet | ||
</a> | ||
{:else} | ||
<h2 class="text-center"> | ||
Scan the QR code using your <b><u>eID App</u></b> to login | ||
</h2> | ||
<article | ||
class="overflow-hidden rounded-2xl" | ||
use:qrcode={{ | ||
data: qrData, | ||
width: 250, | ||
height: 250, | ||
margin: 12, | ||
type: 'canvas', | ||
dotsOptions: { | ||
type: 'rounded', | ||
color: '#fff' | ||
}, | ||
backgroundOptions: { | ||
gradient: { | ||
type: 'linear', | ||
rotation: 50, | ||
colorStops: [ | ||
{ offset: 0, color: '#4D44EF' }, | ||
{ offset: 0.65, color: '#F35B5B' }, | ||
{ offset: 1, color: '#F7A428' } | ||
] | ||
} | ||
} | ||
} | ||
}} | ||
></article> | ||
<a href={qrData}>{qrData}</a> | ||
}} | ||
></article> | ||
{/if} | ||
{/if} | ||
<p> | ||
|
||
<p class="text-center"> | ||
<span class="mb-1 block font-bold text-gray-600">The code is valid for 60 seconds</span> | ||
<span class="block font-light text-gray-600">Please refresh the page if it expires</span | ||
> | ||
</p> | ||
<p class="w-[350px] bg-white/60 p-4 leading-4 text-black/60"> | ||
|
||
<p class="w-full rounded-md bg-white/60 p-4 text-sm leading-4 text-black/60"> | ||
You are entering Pictique - a social network built on the Web 3.0 Data Space (W3DS) | ||
architecture. This system is designed around the principle of data-platform separation, | ||
where all your personal content is stored in your own sovereign eVault, not on | ||
centralised servers. | ||
</p> | ||
</div> | ||
|
||
<W3dslogo /> | ||
</div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider adding window resize listener for responsive behavior.
Unlike the Pictique implementation, this component doesn't listen for window resize events. Users who rotate their device or resize their browser window won't see the UI update accordingly.
Add a resize event listener to update mobile state dynamically:
🤖 Prompt for AI Agents