-
-
Notifications
You must be signed in to change notification settings - Fork 141
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
afd7be0
commit b8374e7
Showing
7 changed files
with
422 additions
and
202 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { PropsWithChildren, useState } from 'react' | ||
import { IonContent, IonHeader, IonImg, IonPage, IonTitle, IonToolbar } from '@ionic/react'; | ||
import raven_logo from '../../assets/raven_logo.png' | ||
import { LoginWithEmail } from '@/pages/auth/LoginWithEmail'; | ||
import { Login } from '@/pages/auth/Login'; | ||
|
||
|
||
const AuthContainer = ({ children, ...props }: PropsWithChildren) => { | ||
const [isLoginWithEmailScreen, setIsLoginWithEmailScreen] = useState<boolean>(false) | ||
|
||
return ( | ||
<IonPage> | ||
<IonHeader translucent> | ||
<IonToolbar> | ||
<IonTitle>Raven</IonTitle> | ||
</IonToolbar> | ||
</IonHeader> | ||
<IonContent className='ion-padding'> | ||
<div className="left-0 right-0 top-1/4 p-2 transform justify-center items-center"> | ||
<IonHeader collapse="condense" translucent> | ||
<IonToolbar> | ||
<IonImg src={raven_logo} alt="Raven Logo" className="block m-auto mb-4 w-40" /> | ||
</IonToolbar> | ||
</IonHeader> | ||
|
||
<div className='w-100'> | ||
{ | ||
isLoginWithEmailScreen ? <LoginWithEmail setIsLoginWithEmailScreen={setIsLoginWithEmailScreen}/> : <Login setIsLoginWithEmailScreen={setIsLoginWithEmailScreen}/> | ||
} | ||
</div> | ||
</div> | ||
</IonContent> | ||
</IonPage> | ||
) | ||
|
||
} | ||
|
||
export default AuthContainer; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,128 +1,171 @@ | ||
import { IonButton, IonContent, IonHeader, IonImg, IonInput, IonItem, IonPage, IonSpinner, IonTitle, IonToolbar, IonText, IonRow } from '@ionic/react' | ||
import { ErrorBanner } from '../../components/layout' | ||
import {SuccessCallout, CalloutObject} from '@/components/common/SuccessCallout' | ||
import raven_logo from '../../assets/raven_logo.png' | ||
import { useContext, useState } from 'react' | ||
import { UserContext } from '../../utils/auth/UserProvider' | ||
import { IonButton, IonInput, IonItem, IonSpinner, IonText } from '@ionic/react' | ||
import { ErrorCallout } from '@/components/common/Callouts' | ||
import { useState } from 'react' | ||
import { Controller, useForm } from 'react-hook-form' | ||
import { LoginWithEmail } from '@/pages/auth/LoginWithEmail' | ||
import { BiLogoFacebookCircle, BiLogoGithub, BiLogoGoogle, BiMailSend } from 'react-icons/bi' | ||
import { useFrappeGetCall, FrappeError, useFrappeAuth, AuthResponse } from 'frappe-react-sdk' | ||
import { LoginContext, LoginInputs } from '@/types/Auth/Login' | ||
import { TwoFactor } from './TwoFactor' | ||
import { LoginWithEmailProps } from './LoginWithEmail' | ||
|
||
type Inputs = { | ||
email: string, | ||
password: string | ||
const SocialProviderIcons = { | ||
"github": <BiLogoGithub size="18" />, | ||
"google": <BiLogoGoogle size="18" />, | ||
"facebook": <BiLogoFacebookCircle size="18" /> | ||
} | ||
|
||
export const Login = () => { | ||
interface SocialProvider { | ||
name: 'github' | 'google' | 'facebook' | ||
provider_name: string, | ||
auth_url: string, | ||
redirect_to: string, | ||
icon: { | ||
src: string, | ||
alt: string | ||
}, | ||
} | ||
|
||
const [error, setError] = useState<Error | null>(null) | ||
const { login } = useContext(UserContext) | ||
const { control, handleSubmit, formState: { errors } } = useForm<Inputs>() | ||
const [loading, setLoading] = useState(false) | ||
const [callout, setCallout] = useState<CalloutObject | null>(null) | ||
const [isLoginWithEmailLink, setIsLoginWithEmailLink] = useState<boolean>(false) | ||
|
||
// to show/unshow login with email section | ||
const onClickLoginWithEmail = () =>{ | ||
setError(null) | ||
setCallout(null) | ||
setIsLoginWithEmailLink(!isLoginWithEmailLink) | ||
} | ||
export const Login = (props: LoginWithEmailProps) => { | ||
|
||
const { control, handleSubmit,formState: { errors,isSubmitting } } = useForm<LoginInputs>() | ||
// GET call for Login Context (settings for social logins, email link etc) | ||
const { data: loginContext, mutate } = useFrappeGetCall<LoginContext>('raven.api.login.get_context', { | ||
"redirect-to": "/raven" | ||
}, 'raven.api.login.get_context', { | ||
revalidateIfStale: false, | ||
revalidateOnReconnect: false, | ||
revalidateOnFocus: false | ||
}) | ||
const [error, setError] = useState<FrappeError | null>(null) | ||
|
||
const { login } = useFrappeAuth() | ||
// 2FA switch enabled and 2FA response | ||
const [isTwoFactorEnabled, setIsTwoFactorEnabled] = useState<boolean>(false) | ||
const [loginWithTwoFAResponse, setLoginWithTwoFAResponse] = useState<AuthResponse | null>(null) | ||
|
||
async function onSubmit(values: Inputs) { | ||
|
||
async function onSubmit(values: LoginInputs) { | ||
setError(null) | ||
setLoading(true) | ||
return login(values.email, values.password).catch((error) => { setError(error) }).finally(() => setLoading(false)) | ||
if (loginContext?.message?.two_factor_is_enabled) { | ||
// first 2FA call to send temp id and verification to user | ||
return login({ username: values.email, password: values.password }).then((res: AuthResponse) => { | ||
if (res?.verification && res?.tmp_id) { | ||
setIsTwoFactorEnabled(true) | ||
setLoginWithTwoFAResponse(res) | ||
} | ||
}).catch((error) => setError(error)) | ||
} else { | ||
// if 2FA is disabled, do normal login | ||
return login({ username: values.email, password: values.password }).then(() => { | ||
//Reload the page so that the boot info is fetched again | ||
const URL = import.meta.env.VITE_BASE_NAME ? `/${import.meta.env.VITE_BASE_NAME}` : `` | ||
window.location.replace(`${URL}/channel`) | ||
}).catch((error) => { setError(error) }) | ||
} | ||
} | ||
|
||
return ( | ||
<IonPage> | ||
<IonHeader translucent> | ||
<IonToolbar> | ||
<IonTitle>Raven</IonTitle> | ||
</IonToolbar> | ||
</IonHeader> | ||
<IonContent className='ion-padding'> | ||
<div slot='fixed' className="left-0 right-0 top-1/4 p-2 transform justify-center items-center"> | ||
<IonHeader collapse="condense" translucent> | ||
<IonToolbar> | ||
<IonImg src={raven_logo} alt="Raven Logo" className="block m-auto mb-4 w-40" /> | ||
</IonToolbar> | ||
</IonHeader> | ||
{error && <ErrorBanner overrideHeading={error.message} />} | ||
{ callout && <SuccessCallout message={callout.message}/> } | ||
{ | ||
isLoginWithEmailLink ? | ||
<LoginWithEmail | ||
setCallout={setCallout} | ||
setError={setError} | ||
onClickLoginWithEmail={onClickLoginWithEmail} | ||
/>: | ||
<div> | ||
<form onSubmit={handleSubmit(onSubmit)}> | ||
<IonItem> | ||
<Controller | ||
name="email" | ||
control={control} | ||
rules={{ | ||
required: "Email/Username is required", | ||
}} | ||
render={({ field }) => <IonInput | ||
onIonChange={(e) => field.onChange(e.detail.value)} | ||
required | ||
placeholder='jane@example.com' | ||
className={!!errors?.email ? 'ion-invalid ion-touched' : ''} | ||
label='Email/Username' | ||
errorText={errors?.email?.message} | ||
inputMode='email' | ||
labelPlacement='stacked' | ||
/>} | ||
/> | ||
</IonItem> | ||
<IonItem> | ||
<Controller | ||
name="password" | ||
control={control} | ||
rules={{ | ||
required: "Password is required." | ||
}} | ||
render={({ field }) => <IonInput | ||
type="password" | ||
onIonChange={(e) => field.onChange(e.detail.value)} | ||
required | ||
errorText={errors?.password?.message} | ||
placeholder='********' | ||
className={!!errors?.password ? 'ion-invalid ion-touched' : ''} | ||
label='Password' | ||
labelPlacement='stacked' | ||
/>} | ||
/> | ||
</IonItem> | ||
<IonButton | ||
type="submit" | ||
className='ion-margin-top' | ||
expand="block" | ||
> | ||
{loading ? <IonSpinner name="crescent" /> : "Login"} | ||
</IonButton> | ||
</form> | ||
<IonRow class="ion-justify-content-center ion-margin-top ion-margin-bottom"> | ||
<IonText color="medium" > | ||
or | ||
</IonText> | ||
</IonRow> | ||
<IonButton | ||
type="button" | ||
onClick={onClickLoginWithEmail} | ||
expand="block" | ||
> | ||
{loading ? <IonSpinner name="crescent" /> : "Login With Email Link"} | ||
</IonButton> | ||
</div> | ||
} | ||
|
||
</div> | ||
</IonContent> | ||
<> | ||
{error && <ErrorCallout message={error.message} />} | ||
{ | ||
isTwoFactorEnabled ? <TwoFactor loginWithTwoFAResponse={loginWithTwoFAResponse} setIsTwoFactorEnabled={setIsTwoFactorEnabled} /> : | ||
<div> | ||
<form onSubmit={handleSubmit(onSubmit)}> | ||
<IonItem> | ||
<Controller | ||
name="email" | ||
control={control} | ||
rules={{ | ||
required: "Email/Username is required", | ||
}} | ||
render={({ field }) => <IonInput | ||
onIonChange={(e) => field.onChange(e.detail.value)} | ||
required | ||
placeholder='jane@example.com' | ||
className={!!errors?.email ? 'ion-invalid ion-touched' : ''} | ||
label='Email/Username' | ||
errorText={errors?.email?.message} | ||
inputMode='email' | ||
labelPlacement='stacked' | ||
/>} | ||
/> | ||
</IonItem> | ||
<IonItem> | ||
<Controller | ||
name="password" | ||
control={control} | ||
rules={{ | ||
required: "Password is required." | ||
}} | ||
render={({ field }) => <IonInput | ||
type="password" | ||
onIonChange={(e) => field.onChange(e.detail.value)} | ||
required | ||
errorText={errors?.password?.message} | ||
placeholder='********' | ||
className={!!errors?.password ? 'ion-invalid ion-touched' : ''} | ||
label='Password' | ||
labelPlacement='stacked' | ||
/>} | ||
/> | ||
</IonItem> | ||
<IonButton | ||
type="submit" | ||
className='ion-margin-top' | ||
expand="block" | ||
> | ||
{isSubmitting ? <IonSpinner name="crescent" /> : "Login"} | ||
</IonButton> | ||
</form> | ||
{/* Show Separator only when either Email Link or Social Logins are enabled */} | ||
{ | ||
loginContext?.message?.login_with_email_link || loginContext?.message?.social_login ? | ||
// <IonRow className="mt-8 mb-8"> | ||
<div className="flex flex-col w-full items-center"> | ||
<IonText>OR</IonText> | ||
</div> | ||
: null | ||
} | ||
{/* Map all social oauth providers */} | ||
{ | ||
loginContext?.message?.social_login ? loginContext?.message?.provider_logins.map((soc: SocialProvider, i: number) => { | ||
return ( | ||
<IonButton className='ion-margin-top' fill="outline" type="button" expand="block" size="default" href={soc.auth_url}> | ||
{/* <Link to={soc.auth_url} className="items-center"> */} | ||
<div className='flex items-center'> | ||
<div className='flex mr-1'> | ||
{SocialProviderIcons[soc.name] ? SocialProviderIcons[soc.name] : <img src={soc.icon.src} alt={soc.icon.alt} ></img>} | ||
</div> | ||
<IonText color="dark">Login with {soc.provider_name}</IonText> | ||
</div> | ||
|
||
{/* </Link> */} | ||
</IonButton> | ||
|
||
) | ||
}) : null | ||
} | ||
|
||
</IonPage> | ||
{ | ||
loginContext?.message?.login_with_email_link ? | ||
|
||
<IonButton | ||
disabled={isSubmitting} | ||
expand="block" | ||
className="ion-margin-top cursor-default" | ||
fill='outline' | ||
type='button' | ||
size="default" | ||
onClick={()=>props.setIsLoginWithEmailScreen(true)} | ||
> | ||
<BiMailSend size="18" className="mr-1" /> | ||
<IonText color="dark">Login with Email Link</IonText> | ||
</IonButton> | ||
: null | ||
} | ||
</div> | ||
} | ||
</> | ||
) | ||
} | ||
} |
Oops, something went wrong.