150 lines
4.2 KiB
TypeScript
150 lines
4.2 KiB
TypeScript
import React, { useEffect, useState } from 'react';
|
||
|
||
interface Props {
|
||
theme: 'light'|'dark';
|
||
onToggleTheme: ()=>void;
|
||
onLogin: ()=>void;
|
||
market?: string;
|
||
}
|
||
|
||
export const LoginScreen: React.FC<Props> = ({ theme, onToggleTheme, onLogin, market='en-US' }) => {
|
||
const [bgUrl,setBgUrl] = useState<string|null>(null);
|
||
const [attribution,setAttribution] = useState<string>('');
|
||
|
||
useEffect(()=>{
|
||
let cancelled = false;
|
||
fetch(`https://www.bing.com/HPImageArchive.aspx?format=js&idx=0&n=1&mkt=${market}`)
|
||
.then(r=> r.ok ? r.json(): Promise.reject(r.status))
|
||
.then(json=>{
|
||
if (cancelled) return;
|
||
if (json?.images?.[0]) {
|
||
const img = json.images[0];
|
||
setBgUrl('https://www.bing.com' + img.url);
|
||
setAttribution(img.copyright || '');
|
||
}
|
||
})
|
||
.catch(()=>{ /* silent fallback */ });
|
||
return ()=> { cancelled = true; };
|
||
}, [market]);
|
||
|
||
const providerName: string = (typeof __OIDC_PROVIDER__ !== 'undefined' && __OIDC_PROVIDER__) || 'Identity Provider';
|
||
|
||
return (
|
||
<div style={{
|
||
position:'relative',
|
||
minHeight:'100vh',
|
||
fontFamily:'system-ui, sans-serif',
|
||
color:'var(--color-text)',
|
||
background: bgUrl
|
||
? `center/cover no-repeat url(${bgUrl}) fixed`
|
||
: 'linear-gradient(135deg,var(--grad-a),var(--grad-b))'
|
||
}}>
|
||
<div style={{
|
||
position:'absolute',
|
||
inset:0,
|
||
background:'var(--overlay)'
|
||
}}/>
|
||
<div style={{
|
||
position:'relative',
|
||
zIndex:2,
|
||
display:'flex',
|
||
flexDirection:'column',
|
||
minHeight:'100vh'
|
||
}}>
|
||
<header style={{
|
||
display:'flex',
|
||
justifyContent:'flex-end',
|
||
padding:'16px 20px',
|
||
gap:12
|
||
}}>
|
||
<button
|
||
onClick={onToggleTheme}
|
||
style={iconButtonStyle}
|
||
aria-label="Toggle dark mode"
|
||
title="Toggle dark mode"
|
||
>
|
||
{theme==='dark'
|
||
? '☀️'
|
||
: '🌙'}
|
||
</button>
|
||
</header>
|
||
<main style={{
|
||
flex:1,
|
||
display:'flex',
|
||
alignItems:'center',
|
||
justifyContent:'center',
|
||
padding:24
|
||
}}>
|
||
<div style={cardStyle}>
|
||
<h1 style={{ margin:'0 0 8px', fontSize:32, lineHeight:1.1 }}>IMAP Client</h1>
|
||
<p style={{ margin:'0 0 24px', opacity:0.85 }}>
|
||
Secure self‑hosted mail dashboard. Sign in with your identity provider.
|
||
</p>
|
||
<button
|
||
onClick={onLogin}
|
||
style={primaryButtonStyle}
|
||
>
|
||
{`Sign in with ${providerName}`}
|
||
</button>
|
||
{attribution && (
|
||
<div style={{ marginTop:18, fontSize:11, opacity:0.6, textWrap:'balance' }}>
|
||
{attribution}
|
||
</div>
|
||
)}
|
||
</div>
|
||
</main>
|
||
<footer style={{
|
||
position:'relative',
|
||
zIndex:2,
|
||
textAlign:'center',
|
||
padding:'12px 8px',
|
||
fontSize:12,
|
||
color:'var(--color-footer)'
|
||
}}>
|
||
© {new Date().getFullYear()} IMAP Client • Background: Bing Image of the Day
|
||
</footer>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
const cardStyle: React.CSSProperties = {
|
||
width:'min(420px, 100%)',
|
||
backdropFilter:'blur(16px)',
|
||
background:'var(--card-bg)',
|
||
border:'1px solid var(--card-border)',
|
||
padding:'32px 34px 40px',
|
||
borderRadius:20,
|
||
boxShadow:'0 8px 32px -4px rgba(0,0,0,0.35)',
|
||
};
|
||
|
||
const primaryButtonStyle: React.CSSProperties = {
|
||
all:'unset',
|
||
cursor:'pointer',
|
||
background:'linear-gradient(90deg,var(--btn-a),var(--btn-b))',
|
||
color:'#fff',
|
||
fontWeight:600,
|
||
padding:'14px 22px',
|
||
borderRadius:12,
|
||
fontSize:15,
|
||
letterSpacing:0.3,
|
||
textAlign:'center',
|
||
boxShadow:'0 4px 18px -4px rgba(0,0,0,0.4)',
|
||
transition:'transform .15s ease, box-shadow .15s ease'
|
||
};
|
||
|
||
const iconButtonStyle: React.CSSProperties = {
|
||
all:'unset',
|
||
cursor:'pointer',
|
||
width:44,
|
||
height:44,
|
||
display:'grid',
|
||
placeItems:'center',
|
||
fontSize:20,
|
||
background:'var(--icon-btn-bg)',
|
||
border:'1px solid var(--icon-btn-border)',
|
||
borderRadius:12,
|
||
boxShadow:'0 4px 14px -4px rgba(0,0,0,0.4)',
|
||
transition:'background .2s'
|
||
};
|