// Utils import { linkClasses, horFlexClasses, horFlexClassesNoSm, capitalize, getSearchParam, } from '@freesewing/utils' // Context import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus' // Hooks import React, { useState, useEffect, useContext } from 'react' import { useAccount } from '@freesewing/react/hooks/useAccount' import { useBackend } from '@freesewing/react/hooks/useBackend' // Components import { Link } from '@freesewing/react/components/Link' import { Popout } from '@freesewing/react/components/Popout' import { Spinner } from '@freesewing/react/components/Spinner' import { EmailIcon, KeyIcon, LockIcon, WarningIcon, GoogleIcon, GitHubIcon, FreeSewingIcon, UserIcon, } from '@freesewing/react/components/Icon' import { MfaInput, StringInput, PasswordInput } from '@freesewing/react/components/Input' import { H1, H2, H3, H4 } from '@freesewing/react/components/Heading' /* * This SignIn component holds the entire sign-in form * * @param {object} props - All React props * @param {function} props.onSuccess - Optional: A method to run when the sign in is successful * @param {function} props.silent - Optional: Silently login the user if we have a valid session */ export const SignIn = ({ onSuccess = false, silent = false }) => { const { account, setAccount, setToken, seenUser, setSeenUser } = useAccount() const backend = useBackend() const { setLoadingStatus } = useContext(LoadingStatusContext) const [username, setUsername] = useState('') const [password, setPassword] = useState('') const [magicLink, setMagicLink] = useState(true) const [signInFailed, setSignInFailed] = useState(false) const [magicLinkSent, setMagicLinkSent] = useState(false) const [seenBefore, setSeenBefore] = useState(false) const [mfa, setMfa] = useState(false) const [mfaCode, setMfaCode] = useState('') useEffect(() => { if (typeof window !== 'undefined' && signInFailed) { window.setTimeout(() => setSignInFailed(false), 1750) } }, [signInFailed]) // Avoid SSR rendering mismatch by setting this in effect useEffect(() => { if (silent) { const checkSession = async () => { const result = await backend.ping() const [status, body] = Array.isArray(result) ? result : [false, false] // Silent sign-in succeeded if (status === 200) { setAccount(body.account) setSeenUser(body.account.username) setLoadingStatus([true, `Welcome back ${body.account.username}`, true, true]) // Call the onSuccess handler if (typeof onSuccess === 'function') onSuccess(body) } } checkSession() } if (seenUser) { setSeenBefore(seenUser) setUsername(seenUser) } else { setSeenBefore(false) setUsername('') } }, [seenUser]) const triggerSubmit = (evt) => { if (evt.key === 'Enter') signinHandler(evt) } const signinHandler = async (evt) => { evt.preventDefault() setLoadingStatus([true, 'Contacting FreeSewing backend']) const result = magicLink ? await backend.signIn({ username, password: false }) : await backend.signIn({ username, password, token: mfaCode }) const [status, body] = Array.isArray(result) ? result : [false, false] if (!status) { setSignInFailed('Unexpected error when attempting sign-in') return setLoadingStatus([ true, 'Unexpected error when attempting sign-in. Please report this.', true, false, ]) } // Sign-in succeeded if (status === 200) { if (magicLink) { setLoadingStatus([true, 'Email sent', true, true]) setMagicLinkSent(true) } else { setAccount(body.account) setToken(body.token) setSeenUser(body.account.username) setLoadingStatus([true, `Welcome back ${body.account.username}`, true, true]) // Call the onSuccess handler if (typeof onSuccess === 'function') onSuccess(body) } } // Sign-in failed if (status === 401) { const msg = magicLink ? 'Unable to find this user' : 'Sign-In failed' setSignInFailed(msg) setLoadingStatus([true, msg, true, false]) } // Bad request if (status === 400) { let msg if (result.data.error === 'usernameMissing') msg = 'Please provide your username' else if (result.data.error === 'passwordMissing') msg = 'Please provide your password' setSignInFailed(msg) setLoadingStatus([true, msg, true, false]) } // MFA active if (status === 403 && body.error === 'mfaTokenRequired') { setMfa(true) setLoadingStatus([ true, 'Please provide a one-time MFA code, or a backup scratch code', true, true, ]) } } const initOauth = async (provider) => { setLoadingStatus([true, 'Contacting the FreeSewing backend']) const [status, body] = await backend.oauthInit(provider.toLowerCase()) if (status === 200 && body.result === 'success') { setLoadingStatus([true, `Contacting ${capitalize(provider)}`]) window.location.href = body.authUrl } } const btnClasses = `tw-daisy-btn tw-capitalize tw-w-full tw-mt-4 ${ signInFailed ? 'tw-daisy-btn-warning' : 'tw-daisy-btn-primary' } tw-transition-colors tw-ease-in-out tw-duration-300 ${horFlexClassesNoSm}` const noBueno = ( <> {signInFailed} ) if (magicLinkSent) return (

Email Sent

Go check your inbox for an email from FreeSewing.org

Click the sign-in link in that email to sign in to your FreeSewing account.

Contact support
) if (mfa) return ( Contact support } /> ) return (

{seenBefore ? `Welcome back ${seenUser}` : 'Welcome'}

Sign in to FreeSewing

{!seenBefore && ( val.length > 1} /> )} {magicLink ? ( ) : ( <> val.length > 0} onKeyDown={triggerSubmit} /> )}
{['Google', 'Github'].map((provider) => ( ))}
{seenBefore ? ( ) : ( Sign up here )}
) } const WrapForm = ({ children }) =>
{children}
const MfaForm = ({ mfaCode, setMfaCode, onSubmit, post = [] }) => (

MFA Code

Please provide a one-time MFA code, or a backup scratch code

{post}
) export const SignInConfirmation = ({ onSuccess = false }) => { // State const [error, setError] = useState(false) const [id, setId] = useState() const [check, setCheck] = useState() const [mfa, setMfa] = useState() const [mfaCode, setMfaCode] = useState() // Hooks const { setAccount, setToken } = useAccount() const backend = useBackend() // Context const { setLoadingStatus } = useContext(LoadingStatusContext) // Effects useEffect(() => { const newId = getSearchParam('id') const newCheck = getSearchParam('check') if (newId !== id) setId(newId) if (newCheck !== check) setCheck(newCheck) }, [id, check]) useEffect(() => { // Call async method if (id) getConfirmation() }, [id]) // Gets the confirmation const getConfirmation = async () => { setLoadingStatus([true, 'Contacting the backend', true, true]) // Reach out to backend const [status, body] = await backend.signInFromLink({ id, check, token: mfaCode }) // If it works, store account, which runs the onSuccess handler if (body.token) return storeAccount(body) // If it did not work, perhaps we need to handle MFA? if (status === 403 && body.error === 'mfaTokenRequired') { setMfa(true) setLoadingStatus([ true, 'Please provide a one-time MFA code, or a backup scratch code', true, true, ]) return } // If we get here, we're not sure what's wrong if (body.error) return setError(body.error) return setError(true) } // Updates the (local) account data const storeAccount = async (data) => { if (data?.token && data?.account) { setToken(data.token) setAccount(data.account) if (typeof onSuccess === 'function') onSuccess(data) else navigate('/account', true) } else { setError(data) } } // Short-circuit errors if (error && mfa) return error === 'signInFailed' ? ( <>

Your one-time token is either invalid of expired.

) : ( <>

This error is unexpected. Please report this.

) // Show MFA input if required if (mfa) return // If we do not (yet) have the data, show a loader if (!id || !check) return ( <>

One moment please

) return

fixme

} /* * This is the generic component that will handle the Oauth callback */ function OauthCallback({ provider = false }) { const [error, setError] = useState(false) const backend = useBackend() const { setAccount, setToken, setSeenUser } = useAccount() // Handle callback useEffect(() => { const oauthFlow = async () => { const state = getSearchParam('state') const code = getSearchParam('state') const [status, body] = await backend.oauthSignIn({ state, code, provider }) if (status === 200 && body.account && body.token) { setAccount(body.account) setToken(body.token) setSeenUser(body.data.account.username) navigate('/welcome', true) } else setError(true) } oauthFlow() }, []) if (!provider) return

You must provide a provider prop to this component

return error ? ( <>

Oh no, something went wrong

Please{' '} escalate this to support

) : ( <>

One moment please

) }