mirror of
https://github.com/siv-org/siv.git
synced 2026-01-10 10:57:59 -05:00
login: Create enter-login-code page
This commit is contained in:
1
pages/enter-login-code.tsx
Normal file
1
pages/enter-login-code.tsx
Normal file
@@ -0,0 +1 @@
|
||||
export { EnterCodePage as default } from '../src/admin/LoginPage/EnterCodePage'
|
||||
108
src/admin/LoginPage/EnterCodePage.tsx
Normal file
108
src/admin/LoginPage/EnterCodePage.tsx
Normal file
@@ -0,0 +1,108 @@
|
||||
import { TextField } from '@material-ui/core'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useState } from 'react'
|
||||
import { GlobalCSS } from 'src/GlobalCSS'
|
||||
import { OnClickButton } from 'src/landing-page/Button'
|
||||
|
||||
import { api } from '../../api-helper'
|
||||
import { Head } from '../../Head'
|
||||
import { checkLoginCode } from '../auth'
|
||||
import { Headerbar } from './Headerbar'
|
||||
|
||||
export const breakpoint = 500
|
||||
|
||||
export const EnterCodePage = () => {
|
||||
const router = useRouter()
|
||||
const { email } = router.query
|
||||
const [error, setError] = useState('')
|
||||
const [loginCode, setLoginCode] = useState('')
|
||||
|
||||
if (typeof email !== 'string') return <p>Missing email</p>
|
||||
|
||||
return (
|
||||
<main>
|
||||
<Head title="Admin Login" />
|
||||
<Headerbar hideLogin />
|
||||
<section>
|
||||
<p>An email with login information is being sent to:</p>
|
||||
<h4>{email}</h4>
|
||||
|
||||
<div>
|
||||
<TextField
|
||||
autoFocus
|
||||
label="Code in Email"
|
||||
placeholder="123456"
|
||||
size="small"
|
||||
style={{ backgroundColor: '#fff' }}
|
||||
value={loginCode}
|
||||
variant="outlined"
|
||||
onChange={({ target }) => {
|
||||
setError('')
|
||||
const next = target.value
|
||||
// Allow up to 6 numbers only
|
||||
if (/^\d{0,6}$/.test(next)) return setLoginCode(next)
|
||||
setError('Login codes are 6 digit numbers')
|
||||
}}
|
||||
/>
|
||||
<OnClickButton
|
||||
style={{ margin: 0, marginLeft: 10, padding: '8px 20px' }}
|
||||
onClick={() => {
|
||||
checkLoginCode({
|
||||
code: loginCode,
|
||||
email,
|
||||
onExpired: () => {
|
||||
setError('This login link has expired.\nSending you another...')
|
||||
setLoginCode('')
|
||||
api('admin-login', { email })
|
||||
},
|
||||
onInvalid: () => setError('Incorrect code'),
|
||||
router,
|
||||
})
|
||||
}}
|
||||
>
|
||||
Submit
|
||||
</OnClickButton>
|
||||
</div>
|
||||
<div className="error" style={{ opacity: error ? '' : 0 }}>
|
||||
⚠️ {error}
|
||||
</div>
|
||||
</section>
|
||||
<style jsx>{`
|
||||
section {
|
||||
text-align: center;
|
||||
margin-top: 3rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin: 2rem 0;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: red;
|
||||
opacity: 0.75;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
|
||||
border: 1px solid #f00a;
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
|
||||
margin-top: 1rem;
|
||||
display: inline-block;
|
||||
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
`}</style>
|
||||
<style global jsx>{`
|
||||
body {
|
||||
background: #f9fafb;
|
||||
}
|
||||
`}</style>
|
||||
<GlobalCSS />
|
||||
</main>
|
||||
)
|
||||
}
|
||||
@@ -1,14 +1,16 @@
|
||||
import { LoginInput } from './LoginInput'
|
||||
import { breakpoint } from './LoginPage'
|
||||
|
||||
export const Headerbar = () => {
|
||||
export const Headerbar = ({ hideLogin }: { hideLogin?: boolean }) => {
|
||||
return (
|
||||
<header>
|
||||
<div>
|
||||
<h2>Secure Internet Voting</h2>
|
||||
<section>
|
||||
<LoginInput />
|
||||
</section>
|
||||
{!hideLogin && (
|
||||
<section>
|
||||
<LoginInput />
|
||||
</section>
|
||||
)}
|
||||
</div>
|
||||
<style jsx>{`
|
||||
header {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { TextField } from '@material-ui/core'
|
||||
import { validate as validateEmail } from 'email-validator'
|
||||
import { useRouter } from 'next/router'
|
||||
import { ChangeEventHandler, KeyboardEventHandler, useRef, useState } from 'react'
|
||||
import { api } from 'src/api-helper'
|
||||
import { OnClickButton } from 'src/landing-page/Button'
|
||||
@@ -13,6 +14,7 @@ type SharedInputProps = {
|
||||
}
|
||||
|
||||
export const LoginInput = ({ mobile }: { mobile?: boolean }) => {
|
||||
const router = useRouter()
|
||||
const [email, setEmail] = useState('')
|
||||
const [error, setError] = useState('')
|
||||
const [pending, setPending] = useState(false)
|
||||
@@ -63,7 +65,7 @@ export const LoginInput = ({ mobile }: { mobile?: boolean }) => {
|
||||
} else if (response.status === 404) {
|
||||
setError('Not an approved account. \nCheck for typos, or Create Account below.')
|
||||
} else {
|
||||
alert('TODO: Switch to Enter Code page')
|
||||
router.push(`./enter-login-code?email=${email}`)
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import { useRouter } from 'next/router'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useEffect } from 'react'
|
||||
import { GlobalCSS } from 'src/GlobalCSS'
|
||||
import { OnClickButton } from 'src/landing-page/Button'
|
||||
|
||||
import { api } from '../../api-helper'
|
||||
import { Head } from '../../Head'
|
||||
import { checkLoginCode } from '../auth'
|
||||
import { AboutSection } from './AboutSection'
|
||||
import { CreateAccount } from './CreateAccount'
|
||||
import { Headerbar } from './Headerbar'
|
||||
@@ -14,17 +10,16 @@ import { MobileLogin } from './MobileLogin'
|
||||
export const breakpoint = 500
|
||||
|
||||
export const LoginPage = () => {
|
||||
const router = useRouter()
|
||||
const [email, setEmail] = useState('')
|
||||
const [error, setError] = useState('')
|
||||
const [loginCode, setLoginCode] = useState('')
|
||||
// const router = useRouter()
|
||||
// const [email, setEmail] = useState('')
|
||||
// const [error, setError] = useState('')
|
||||
|
||||
// Check if there's a redirect message in URL
|
||||
useEffect(() => {
|
||||
const { email, expired, invalid } = router.query
|
||||
if (email) setEmail(email as string)
|
||||
if (expired) setError('This login link has expired, click Sign In below to create another.')
|
||||
if (invalid) setError('This login link appears invalid, click Sign In below to create another.')
|
||||
// const { email, expired, invalid } = router.query
|
||||
// if (email) setEmail(email as string)
|
||||
// if (expired) setError('This login link has expired, click Sign In below to create another.')
|
||||
// if (invalid) setError('This login link appears invalid, click Sign In below to create another.')
|
||||
}, [])
|
||||
|
||||
return (
|
||||
@@ -39,57 +34,6 @@ export const LoginPage = () => {
|
||||
<CreateAccount />
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ display: 'none' }}>
|
||||
{status == 'sent' && (
|
||||
<>
|
||||
<div className="text-center">
|
||||
<p className="text-sm">An email with login information is being sent to:</p>
|
||||
<p className="font-semibold">{email}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="mt-8 text-xs font-semibold">Code:</label>
|
||||
<div className="flex">
|
||||
<input
|
||||
className="relative flex-grow block px-3 py-2 text-gray-900 placeholder-gray-500 border border-gray-300 rounded-md appearance-none focus:outline-none focus:ring-blue-500 focus:border-blue-500 focus:z-10"
|
||||
placeholder="123456"
|
||||
type="text"
|
||||
value={loginCode}
|
||||
onChange={({ target }) => {
|
||||
setError('')
|
||||
const next = target.value
|
||||
// Allow up to 6 numbers only
|
||||
if (/^\d{0,6}$/.test(next)) return setLoginCode(next)
|
||||
setError('Login codes are 6 digit numbers')
|
||||
}}
|
||||
/>
|
||||
<OnClickButton
|
||||
style={{ margin: 0, marginLeft: 20, padding: '10px 20px' }}
|
||||
onClick={() => {
|
||||
checkLoginCode({
|
||||
clientSideRedirect: false,
|
||||
code: loginCode,
|
||||
email,
|
||||
onExpired: () => {
|
||||
setError("This login link has expired, we're sending you another.")
|
||||
setLoginCode('')
|
||||
api('admin-login', { email })
|
||||
},
|
||||
onInvalid: () => setError('Incorrect code'),
|
||||
router,
|
||||
})
|
||||
}}
|
||||
>
|
||||
Submit
|
||||
</OnClickButton>
|
||||
</div>
|
||||
<label className="error" style={{ opacity: error ? 1 : 0 }}>
|
||||
⚠️ {error}
|
||||
</label>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<style jsx>{`
|
||||
.container {
|
||||
display: flex;
|
||||
|
||||
@@ -22,14 +22,12 @@ async function logout() {
|
||||
}
|
||||
|
||||
export async function checkLoginCode({
|
||||
clientSideRedirect = true,
|
||||
code,
|
||||
email,
|
||||
onExpired,
|
||||
onInvalid,
|
||||
router,
|
||||
}: {
|
||||
clientSideRedirect?: boolean
|
||||
code: string
|
||||
email: string
|
||||
onExpired: () => void
|
||||
@@ -45,9 +43,7 @@ export async function checkLoginCode({
|
||||
mutate(jwt_api_path)
|
||||
|
||||
// Remove url parameters
|
||||
// Need to do full reload when transitioning in from /login page's Code Input form, otherwise Tailwind's css screws up formatting.
|
||||
clientSideRedirect ? await router.replace('/admin') : (window.location.href = '/admin')
|
||||
return
|
||||
return await router.replace('/admin')
|
||||
}
|
||||
|
||||
// Expired session: redirects back to login page w/ custom error
|
||||
|
||||
Reference in New Issue
Block a user