⚛️ React Implementation
Integrate a beautiful, cyber-minimalist login page that fits perfectly with PramanAuth's biometric focus. Here is a complete setup with a styled glassmorphic biometric login card (Login.jsx) and the main application coordinator (App.jsx).
1. The main application coordinator (App.jsx)
This matches the initialization and auth flow. It manages the login popup and tracks the user state:
import { useState } from 'react';
import { initPraman } from '@praman-network/sdk';
import LoginPage from './Login';
// Initialize with your test credentials
const praman = initPraman({
apiKey: 'pk_test_DOd1GeOt6KW4SzO7EGLIwWYF', // Replace with your actual API key
network: "polygon-amoy",
idpUrl: "https://auth.praman.network",
backendUrl: "https://api.praman.network"
});
function App() {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const handleLogin = async () => {
setLoading(true);
setError(null);
try {
const result = await praman.loginWithPopup({ scopes: ['email', 'profile'] });
if (result.success) {
setUser(result.user);
console.log("User Data:", result.user);
} else {
setError("Authentication failed. Please try again.");
}
} catch (err) {
console.error("Auth failed:", err);
setError(err.message || "An unexpected error occurred.");
} finally {
setLoading(false);
}
};
return (
<div style={{
minHeight: '100vh',
backgroundColor: '#0a0a0c',
color: '#ffffff',
fontFamily: 'Inter, system-ui, sans-serif',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
padding: '20px'
}}>
{!user ? (
<LoginPage
onLogin={handleLogin}
loading={loading}
error={error}
/>
) : (
<div style={{
background: 'rgba(255, 255, 255, 0.03)',
border: '1px solid rgba(255, 255, 255, 0.08)',
borderRadius: '16px',
padding: '32px',
textAlign: 'center',
maxWidth: '400px',
width: '100%',
backdropFilter: 'blur(10px)',
boxShadow: '0 8px 32px 0 rgba(0, 0, 0, 0.37)'
}}>
<div style={{
width: '64px',
height: '64px',
borderRadius: '50%',
background: 'linear-gradient(135deg, #10b981 0%, #059669 100%)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
margin: '0 auto 20px',
fontSize: '24px'
}}>
✓
</div>
<h2 style={{ fontSize: '24px', fontWeight: '600', marginBottom: '8px' }}>Authenticated Successfully</h2>
<p style={{ color: '#9ca3af', fontSize: '14px', marginBottom: '24px', wordBreak: 'break-all' }}>
<strong>DID:</strong> {user.did}
</p>
<button
onClick={() => setUser(null)}
style={{
width: '100%',
padding: '12px',
borderRadius: '8px',
border: '1px solid rgba(255, 255, 255, 0.15)',
background: 'transparent',
color: '#ffffff',
fontWeight: '600',
cursor: 'pointer',
transition: 'all 0.2s ease'
}}
>
Sign Out
</button>
</div>
)}
</div>
);
}
export default App;
2. The styled login component (Login.jsx)
Save this as Login.jsx to build a clean, animated login card:
import React from 'react';
export default function LoginPage({ onLogin, loading, error }) {
return (
<div style={styles.card}>
<div style={styles.glow} />
{/* Header */}
<div style={styles.header}>
<div style={styles.logoContainer}>
<span style={styles.logo}>P</span>
</div>
<h2 style={styles.title}>PramanAuth Test App</h2>
<p style={styles.subtitle}>Decentralized Zero-Knowledge Biometric Authentication</p>
</div>
{/* Biometric Icon & Animation */}
<div style={styles.scannerContainer}>
<div style={{
...styles.scannerRing,
animation: loading ? 'pulse 1.5s infinite ease-in-out' : 'none',
borderColor: loading ? '#6366f1' : 'rgba(255, 255, 255, 0.1)'
}}>
<svg style={{
...styles.fingerprintIcon,
fill: loading ? '#6366f1' : '#a1a1aa'
}} viewBox="0 0 24 24" width="64" height="64">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/>
</svg>
{loading && <div style={styles.scanBar} />}
</div>
</div>
{/* Button & Feedback */}
<div style={styles.actionContainer}>
{error && (
<div style={styles.errorAlert}>
{error}
</div>
)}
<button
onClick={onLogin}
disabled={loading}
style={{
...styles.button,
opacity: loading ? 0.7 : 1,
cursor: loading ? 'not-allowed' : 'pointer'
}}
>
{loading ? (
<span style={styles.loadingText}>
<span style={styles.spinner} /> Verifying Biometrics...
</span>
) : (
"Sign In with PramanAuth"
)}
</button>
</div>
{/* CSS Keyframes injected dynamically */}
<style>{`
@keyframes pulse {
0% { transform: scale(1); box-shadow: 0 0 0 0 rgba(99, 102, 241, 0.4); }
50% { transform: scale(1.03); box-shadow: 0 0 20px 10px rgba(99, 102, 241, 0.1); }
100% { transform: scale(1); box-shadow: 0 0 0 0 rgba(99, 102, 241, 0); }
}
@keyframes scan {
0% { top: 0%; }
50% { top: 100%; }
100% { top: 0%; }
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
`}</style>
</div>
);
}
const styles = {
card: {
position: 'relative',
background: 'rgba(15, 15, 20, 0.7)',
border: '1px solid rgba(255, 255, 255, 0.08)',
borderRadius: '24px',
padding: '40px 32px',
maxWidth: '440px',
width: '100%',
backdropFilter: 'blur(20px)',
boxShadow: '0 20px 40px rgba(0, 0, 0, 0.5)',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
overflow: 'hidden'
},
glow: {
position: 'absolute',
top: '-50%',
left: '-50%',
width: '200%',
height: '200%',
background: 'radial-gradient(circle, rgba(99, 102, 241, 0.05) 0%, transparent 60%)',
pointerEvents: 'none',
zIndex: 0
},
header: {
textAlign: 'center',
marginBottom: '32px',
zIndex: 1
},
logoContainer: {
width: '48px',
height: '48px',
borderRadius: '12px',
background: 'linear-gradient(135deg, #6366f1 0%, #4f46e5 100%)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
margin: '0 auto 16px',
boxShadow: '0 4px 12px rgba(99, 102, 241, 0.3)'
},
logo: {
fontWeight: 'bold',
fontSize: '22px',
color: '#fff'
},
title: {
fontSize: '22px',
fontWeight: '700',
marginBottom: '8px',
color: '#ffffff',
letterSpacing: '-0.025em'
},
subtitle: {
fontSize: '13px',
color: '#8e8e9f',
lineHeight: '1.5',
margin: 0
},
scannerContainer: {
marginBottom: '32px',
zIndex: 1
},
scannerRing: {
position: 'relative',
width: '120px',
height: '120px',
borderRadius: '50%',
border: '2px solid rgba(255, 255, 255, 0.1)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: 'rgba(255, 255, 255, 0.02)',
overflow: 'hidden'
},
fingerprintIcon: {
transition: 'fill 0.3s ease'
},
scanBar: {
position: 'absolute',
left: 0,
width: '100%',
height: '3px',
background: 'linear-gradient(90deg, transparent, #6366f1, transparent)',
boxShadow: '0 0 8px #6366f1',
animation: 'scan 2s infinite linear'
},
actionContainer: {
width: '100%',
zIndex: 1
},
button: {
width: '100%',
padding: '14px',
borderRadius: '12px',
border: 'none',
background: 'linear-gradient(135deg, #6366f1 0%, #4f46e5 100%)',
color: '#ffffff',
fontSize: '15px',
fontWeight: '600',
boxShadow: '0 4px 14px rgba(99, 102, 241, 0.4)',
transition: 'all 0.2s ease',
},
errorAlert: {
background: 'rgba(239, 68, 68, 0.1)',
border: '1px solid rgba(239, 68, 68, 0.2)',
borderRadius: '8px',
padding: '12px',
color: '#f87171',
fontSize: '13px',
marginBottom: '16px',
textAlign: 'center'
},
loadingText: {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: '8px'
},
spinner: {
width: '16px',
height: '16px',
borderRadius: '50%',
border: '2px solid rgba(255, 255, 255, 0.3)',
borderTopColor: '#ffffff',
animation: 'spin 0.8s infinite linear'
}
};
Basic Usage Example
If you just want the minimal init + login flow without the full styled card, here's the simplest version:
import { useEffect } from 'react';
import { initPraman, loginWithPraman } from '@praman-network/sdk';
function App() {
useEffect(() => {
// Initialize with your developer API Key
initPraman({
apiKey: 'pm_dev_your_api_key_here',
backendUrl: 'https://auth.praman.network'
});
}, []);
const handleLogin = async () => {
try {
const authResult = await loginWithPraman();
console.log('User Authenticated:', authResult);
} catch (err) {
console.error('Authentication error:', err);
}
};
return (
<button onClick={handleLogin}>
Sign in with Praman
</button>
);
}
export default App;