Skip to content
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

feat: Add Authenticator and SimpleAuthenticator #152

Merged
merged 1 commit into from
Dec 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions packages/react-keyring/src/Authenticator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import React, {
useState, createContext, useContext, useCallback, useMemo
} from 'react'
import { useKeyring, KeyringContextState, KeyringContextActions } from '@w3ui/react-keyring'

export type AuthenticatorContextState = KeyringContextState & {
email?: string,
submitted: boolean,
handleRegisterSubmit?: (e: React.FormEvent<HTMLFormElement>) => void
}

export type AuthenticatorContextActions = KeyringContextActions & {
setEmail: React.Dispatch<React.SetStateAction<string>>
}

export type AuthenticatorContextValue = [
state: AuthenticatorContextState,
actions: AuthenticatorContextActions
]

export const AuthenticatorContext = createContext<AuthenticatorContextValue>([
{
spaces: [],
submitted: false,
},
{
setEmail: () => { throw new Error('missing set email function') },
loadAgent: async () => { },
unloadAgent: async () => { },
resetAgent: async () => { },
createSpace: async () => { throw new Error('missing keyring context provider') },
setCurrentSpace: async () => { },
registerSpace: async () => { },
cancelRegisterSpace: () => { },
getProofs: async () => []
}
])

export function Authenticator(props: any) {
const [state, actions] = useKeyring()
const { createSpace, registerSpace } = actions
const [email, setEmail] = useState('')
const [submitted, setSubmitted] = useState(false)

const handleRegisterSubmit = useCallback(async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
setSubmitted(true)
try {
await createSpace()
await registerSpace(email)
} catch (err: any) {
throw new Error('failed to register')
} finally {
setSubmitted(false)
}
}, [setSubmitted, createSpace, registerSpace])

const value = useMemo<AuthenticatorContextValue>(() => [
{ ...state, email, submitted, handleRegisterSubmit },
{ ...actions, setEmail }
], [state, actions, email, submitted, handleRegisterSubmit])
return (
<AuthenticatorContext.Provider value={value} {...props} />
)
}

Authenticator.Form = function Form(props: any) {
const [{ handleRegisterSubmit }] = useAuthenticator()
return (
<form onSubmit={handleRegisterSubmit} {...props} />
)
}

Authenticator.EmailInput = function EmailInput(props: any) {
const [{ email }, { setEmail }] = useAuthenticator()
return (
<input type='email' value={email} onChange={e => setEmail(e.target.value)} {...props} />
)
}

Authenticator.CancelButton = function CancelButton(props: any) {
const [, { cancelRegisterSpace }] = useAuthenticator()
return (
<button onClick={() => { cancelRegisterSpace() }} {...props} />
)
}

export function useAuthenticator() {
return useContext(AuthenticatorContext)
}
1 change: 1 addition & 0 deletions packages/react-keyring/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './providers/Keyring'
export * from './Authenticator'
64 changes: 64 additions & 0 deletions packages/react-ui/src/SimpleAuthenticator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import React from 'react'
import { Authenticator, useAuthenticator } from '@w3ui/react-keyring'

export function AuthenticationForm() {
const [{ submitted }] = useAuthenticator()

return (
<Authenticator.Form className="w3ui-simple-authenticator-form">
<div className="email-field">
<label htmlFor='w3ui-simple-authenticator-email'>Email address:</label>
<Authenticator.EmailInput id='w3ui-simple-authenticator-email' required />
</div>
<button className="register" type='submit' disabled={submitted}>Register</button>
</Authenticator.Form>
)
}

export function AuthenticationSubmitted() {
const [{ email }] = useAuthenticator()

return (
<div className="w3ui-simple-authenticator-verify-email">
<h1 className="message">Verify your email address!</h1>
<p className="detail">Click the link in the email we sent to {email} to sign in.</p>
<Authenticator.CancelButton className="cancel">
Cancel
</Authenticator.CancelButton>
</div>
)
}

export function AuthenticationEnsurer({ children }: { children: JSX.Element }) {
const [{ space, submitted }] = useAuthenticator()
if (space?.registered()) {
return children
} else if (submitted) {
return <AuthenticationSubmitted />
} else {
return <AuthenticationForm />
}
}

type SimpleAuthenticatorProps = {
children: JSX.Element
}

export function SimpleAuthenticator({ children }: SimpleAuthenticatorProps) {
return (
<Authenticator>
<AuthenticationEnsurer children={children} />
</Authenticator>
)
}

/**
* Wrapping a component with this HoC ensures an identity exists.
*/
export function withIdentity<C extends React.JSXElementConstructor<P>, P>(Component: C) {
return (props: any) => (
<SimpleAuthenticator>
<Component {...props} />
</SimpleAuthenticator>
)
}
2 changes: 2 additions & 0 deletions packages/react-ui/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export * from './SimpleUploader'
export * from './SimpleUploadsList'
export * from './SimpleAuthenticator'