mirror of
https://github.com/zhigang1992/firecms.git
synced 2026-04-29 04:15:25 +08:00
Replaced FirebaseUi with custom implementation
This commit is contained in:
@@ -8,7 +8,7 @@ import "typeface-rubik";
|
||||
import "typeface-space-mono";
|
||||
|
||||
import {
|
||||
AuthDelegate,
|
||||
FirebaseAuthDelegate,
|
||||
Authenticator,
|
||||
buildCollection,
|
||||
buildSchema,
|
||||
@@ -75,6 +75,8 @@ const productSchema = buildSchema({
|
||||
*/
|
||||
export function CustomCMSApp() {
|
||||
|
||||
const signInOptions = DEFAULT_SIGN_IN_OPTIONS;
|
||||
|
||||
const navigation: NavigationBuilder = ({ user }: NavigationBuilderProps) => ({
|
||||
collections: [
|
||||
buildCollection({
|
||||
@@ -102,8 +104,9 @@ export function CustomCMSApp() {
|
||||
firebaseConfigError
|
||||
} = useInitialiseFirebase({ firebaseConfig });
|
||||
|
||||
const authDelegate: AuthDelegate = useFirebaseAuthDelegate({
|
||||
const authDelegate: FirebaseAuthDelegate = useFirebaseAuthDelegate({
|
||||
firebaseApp,
|
||||
signInOptions
|
||||
});
|
||||
|
||||
const dataSource = useFirestoreDataSource({
|
||||
@@ -149,7 +152,7 @@ export function CustomCMSApp() {
|
||||
component = (
|
||||
<FirebaseLoginView
|
||||
skipLoginButtonEnabled={false}
|
||||
signInOptions={DEFAULT_SIGN_IN_OPTIONS}
|
||||
signInOptions={signInOptions}
|
||||
firebaseApp={firebaseApp}
|
||||
authDelegate={authDelegate}/>
|
||||
);
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import React from "react";
|
||||
|
||||
import { getAnalytics } from "firebase/analytics";
|
||||
import {
|
||||
EmailAuthProvider,
|
||||
GoogleAuthProvider,
|
||||
User as FirebaseUser
|
||||
} from "firebase/auth";
|
||||
import { User as FirebaseUser } from "firebase/auth";
|
||||
import {
|
||||
Authenticator,
|
||||
buildCollection,
|
||||
@@ -154,11 +150,17 @@ function SampleApp() {
|
||||
user,
|
||||
authController
|
||||
}) => {
|
||||
|
||||
if(user?.email?.includes("flanders")){
|
||||
throw Error("Stupid Flanders!");
|
||||
}
|
||||
|
||||
// This is an example of retrieving async data related to the user
|
||||
// and storing it in the user extra field
|
||||
const sampleUserData = await Promise.resolve({
|
||||
roles: ["admin"]
|
||||
});
|
||||
|
||||
authController.setExtra(sampleUserData);
|
||||
console.log("Allowing access to", user);
|
||||
return true;
|
||||
@@ -191,8 +193,14 @@ function SampleApp() {
|
||||
name={"My Online Shop"}
|
||||
authentication={myAuthenticator}
|
||||
signInOptions={[
|
||||
GoogleAuthProvider.PROVIDER_ID,
|
||||
EmailAuthProvider.PROVIDER_ID
|
||||
'password',
|
||||
// 'anonymous',
|
||||
'google.com',
|
||||
// 'facebook.com',
|
||||
'github.com',
|
||||
// 'twitter.com',
|
||||
// 'microsoft.com',
|
||||
// 'apple.com'
|
||||
]}
|
||||
textSearchController={textSearchController}
|
||||
allowSkipLogin={true}
|
||||
|
||||
@@ -232,6 +232,11 @@ export default function App() {
|
||||
user,
|
||||
authController
|
||||
}) => {
|
||||
|
||||
if(user?.email?.includes("flanders")){
|
||||
throw Error("Stupid Flanders!");
|
||||
}
|
||||
|
||||
console.log("Allowing access to", user?.email);
|
||||
// This is an example of retrieving async data related to the user
|
||||
// and storing it in the user extra field.
|
||||
|
||||
@@ -38,7 +38,6 @@
|
||||
"@uiw/react-md-editor": "^3.4.10",
|
||||
"date-fns": "^2.21.3",
|
||||
"deep-equal": "^2.0.5",
|
||||
"firebaseui": "~0.600",
|
||||
"formik": "^2.2.9",
|
||||
"history": "^5",
|
||||
"object-hash": "^2.2.0",
|
||||
|
||||
@@ -185,7 +185,7 @@ export function FireCMS<UserType>(props: FireCMSProps<UserType>) {
|
||||
const schemaRegistryController = useBuildSchemaRegistryController(navigationContext, schemaResolver);
|
||||
const sideEntityController = useBuildSideEntityController(navigationContext, schemaRegistryController);
|
||||
|
||||
const loading = authDelegate.authLoading || navigationContext.loading;
|
||||
const loading = authController.authLoading || authController.initialLoading || navigationContext.loading;
|
||||
|
||||
if (navigationContext.navigationLoadingError) {
|
||||
return (
|
||||
|
||||
@@ -16,7 +16,7 @@ export const useStyles = makeStyles((theme: Theme) =>
|
||||
margin: theme.spacing(1)
|
||||
},
|
||||
text: {
|
||||
paddingLeft: theme.spacing(1)
|
||||
paddingLeft: theme.spacing(2)
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, {lazy, Suspense, useCallback, useEffect, useState} from "react";
|
||||
import {
|
||||
Box,
|
||||
CircularProgress,
|
||||
CircularProgress, Divider,
|
||||
IconButton,
|
||||
Tab,
|
||||
Tabs,
|
||||
@@ -436,6 +436,8 @@ export function EntityView<M extends { [Key: string]: any }, UserType>({
|
||||
}}/>
|
||||
</Tabs>
|
||||
|
||||
<Divider/>
|
||||
|
||||
<Box flexGrow={1}/>
|
||||
|
||||
{dataLoading &&
|
||||
|
||||
@@ -73,6 +73,10 @@ export function FireCMSAppBar({
|
||||
const authController = useAuthController();
|
||||
const { mode, toggleMode } = useModeState();
|
||||
|
||||
const initial = authController.user?.displayName ?
|
||||
authController.user.displayName[0].toUpperCase()
|
||||
: (authController.user?.email ? authController.user.email[0].toUpperCase() : "A");
|
||||
|
||||
return (
|
||||
<Slide
|
||||
direction="down" in={true} mountOnEnter unmountOnExit>
|
||||
@@ -157,7 +161,7 @@ export function FireCMSAppBar({
|
||||
<Avatar
|
||||
src={authController.user.photoURL}/>
|
||||
:
|
||||
<Avatar>{authController.user?.displayName ? authController.user.displayName[0] : "A"}</Avatar>
|
||||
<Avatar>{initial}</Avatar>
|
||||
}
|
||||
</Box>
|
||||
|
||||
|
||||
@@ -26,12 +26,14 @@ export function useBuildAuthController<UserType>({
|
||||
}): AuthController<UserType> {
|
||||
|
||||
const [user, setUser] = useState<User | null>(null);
|
||||
const [authLoading, setAuthLoading] = useState<boolean>(false);
|
||||
const [notAllowedError, setNotAllowedError] = useState<any>(false);
|
||||
const [extra, setExtra] = useState<any>();
|
||||
|
||||
async function checkAuthentication() {
|
||||
const delegateUser = authDelegate.user;
|
||||
if (authentication instanceof Function && delegateUser) {
|
||||
setAuthLoading(true);
|
||||
try {
|
||||
const allowed = await authentication({
|
||||
user: delegateUser,
|
||||
@@ -47,7 +49,9 @@ export function useBuildAuthController<UserType>({
|
||||
setNotAllowedError(true);
|
||||
} catch (e) {
|
||||
setNotAllowedError(e);
|
||||
authDelegate.signOut();
|
||||
}
|
||||
setAuthLoading(false);
|
||||
} else {
|
||||
setUser(delegateUser);
|
||||
}
|
||||
@@ -66,6 +70,8 @@ export function useBuildAuthController<UserType>({
|
||||
user,
|
||||
loginSkipped,
|
||||
canAccessMainView,
|
||||
initialLoading: authDelegate.initialLoading ?? false,
|
||||
authLoading: authLoading ,
|
||||
notAllowedError,
|
||||
signOut: authDelegate.signOut,
|
||||
extra,
|
||||
|
||||
@@ -19,7 +19,7 @@ import { useFirestoreDataSource } from "./hooks/useFirestoreDataSource";
|
||||
import { useFirebaseStorageSource } from "./hooks/useFirebaseStorageSource";
|
||||
import { useInitialiseFirebase } from "./hooks/useInitialiseFirebase";
|
||||
import { FirebaseLoginView } from "./components/FirebaseLoginView";
|
||||
import { AuthDelegate } from "../models";
|
||||
import { FirebaseAuthDelegate } from "./models/auth";
|
||||
|
||||
const DEFAULT_SIGN_IN_OPTIONS = [
|
||||
GoogleAuthProvider.PROVIDER_ID
|
||||
@@ -51,7 +51,7 @@ export function FirebaseCMSApp({
|
||||
navigation,
|
||||
textSearchController,
|
||||
allowSkipLogin,
|
||||
signInOptions,
|
||||
signInOptions = DEFAULT_SIGN_IN_OPTIONS,
|
||||
firebaseConfig,
|
||||
onFirebaseInit,
|
||||
primaryColor,
|
||||
@@ -71,8 +71,9 @@ export function FirebaseCMSApp({
|
||||
firebaseConfigError
|
||||
} = useInitialiseFirebase({ onFirebaseInit, firebaseConfig });
|
||||
|
||||
const authDelegate: AuthDelegate = useFirebaseAuthDelegate({
|
||||
firebaseApp
|
||||
const authDelegate: FirebaseAuthDelegate = useFirebaseAuthDelegate({
|
||||
firebaseApp,
|
||||
signInOptions
|
||||
});
|
||||
|
||||
const dataSource = useFirestoreDataSource({
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
} from "../models";
|
||||
import { FirestoreTextSearchController } from "./models/text_search";
|
||||
import { User as FirebaseUser } from "firebase/auth";
|
||||
import { FirebaseSignInOption, FirebaseSignInProvider } from "./models/auth";
|
||||
|
||||
/**
|
||||
* Main entry point that defines the CMS configuration
|
||||
@@ -52,7 +53,7 @@ export interface FirebaseCMSAppProps {
|
||||
* objects such as specified in https://firebase.google.com/docs/auth/web/firebaseui
|
||||
* Defaults to Google sign in only.
|
||||
*/
|
||||
signInOptions?: Array<string | any>;
|
||||
signInOptions?: Array<FirebaseSignInProvider | FirebaseSignInOption>;
|
||||
|
||||
/**
|
||||
* If authentication is enabled, allow the user to access the content
|
||||
|
||||
@@ -1,24 +1,45 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { Box, Button, Grid, Theme } from "@mui/material";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
CircularProgress,
|
||||
Grid,
|
||||
IconButton,
|
||||
TextField,
|
||||
Theme,
|
||||
Typography
|
||||
} from "@mui/material";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import makeStyles from "@mui/styles/makeStyles";
|
||||
|
||||
import firebase from "firebase/compat/app";
|
||||
import * as firebaseui from "firebaseui";
|
||||
import "firebaseui/dist/firebaseui.css";
|
||||
|
||||
import { FirebaseApp } from "firebase/app";
|
||||
import { FireCMSLogo } from "../../core/components/FireCMSLogo";
|
||||
import { AuthDelegate } from "../../models";
|
||||
import { useAuthController } from "../../hooks";
|
||||
|
||||
import { useAuthController, useModeState } from "../../hooks";
|
||||
import {
|
||||
FirebaseAuthDelegate,
|
||||
FirebaseSignInOption,
|
||||
FirebaseSignInProvider
|
||||
} from "../models/auth";
|
||||
import {
|
||||
appleIcon,
|
||||
facebookIcon,
|
||||
githubIcon,
|
||||
googleIcon,
|
||||
microsoftIcon,
|
||||
twitterIcon
|
||||
} from "./social_icons";
|
||||
import { ErrorView } from "../../core";
|
||||
import EmailIcon from '@mui/icons-material/Email';
|
||||
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||
import PersonOutlineIcon from '@mui/icons-material/PersonOutline';
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
logo: {
|
||||
padding: theme.spacing(3),
|
||||
maxWidth: 260
|
||||
width: 260,
|
||||
height: 260
|
||||
}
|
||||
})
|
||||
);
|
||||
@@ -29,10 +50,9 @@ const useStyles = makeStyles((theme: Theme) =>
|
||||
export interface FirebaseLoginViewProps {
|
||||
skipLoginButtonEnabled?: boolean,
|
||||
logo?: string,
|
||||
// Any of the sign in string or configuration objects defined in https://firebase.google.com/docs/auth/web/firebaseui
|
||||
signInOptions: Array<string | any>;
|
||||
signInOptions: Array<FirebaseSignInProvider | FirebaseSignInOption>;
|
||||
firebaseApp: FirebaseApp;
|
||||
authDelegate: AuthDelegate
|
||||
authDelegate: FirebaseAuthDelegate
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -50,61 +70,45 @@ export function FirebaseLoginView({
|
||||
}: FirebaseLoginViewProps) {
|
||||
const classes = useStyles();
|
||||
const authController = useAuthController();
|
||||
const modeState = useModeState();
|
||||
|
||||
useEffect(() => {
|
||||
if (firebase.apps.length === 0) {
|
||||
try {
|
||||
firebase.initializeApp(firebaseApp.options);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
const [passwordLoginSelected, setPasswordLoginSelected] = useState(false);
|
||||
|
||||
const ui = firebaseui.auth.AuthUI.getInstance() || new firebaseui.auth.AuthUI(firebase.auth());
|
||||
|
||||
const uiConfig: firebaseui.auth.Config = {
|
||||
callbacks: {
|
||||
signInSuccessWithAuthResult: (authResult) => {
|
||||
return true;
|
||||
},
|
||||
signInFailure: async (e) => {
|
||||
console.error("signInFailure", e);
|
||||
}
|
||||
},
|
||||
signInFlow: "popup",
|
||||
signInOptions: signInOptions,
|
||||
credentialHelper: firebaseui.auth.CredentialHelper.GOOGLE_YOLO
|
||||
};
|
||||
ui.start("#firebase-ui", uiConfig);
|
||||
});
|
||||
const resolvedSignInOptions: FirebaseSignInProvider[] = signInOptions.map((o) => {
|
||||
if (typeof o === "object") {
|
||||
return o.provider;
|
||||
} else return o as FirebaseSignInProvider;
|
||||
})
|
||||
|
||||
function buildErrorView() {
|
||||
let errorView: any;
|
||||
const ignoredCodes = ["auth/popup-closed-by-user", "auth/cancelled-popup-request"];
|
||||
if (authDelegate.authError) {
|
||||
if (authDelegate.authError.code === "auth/operation-not-allowed") {
|
||||
errorView =
|
||||
<>
|
||||
<Box p={2}>
|
||||
You need to enable the corresponding login provider
|
||||
in your Firebase project
|
||||
<Box p={1}>
|
||||
<ErrorView
|
||||
error={"You need to enable the corresponding login provider in your Firebase project"}/>
|
||||
</Box>
|
||||
|
||||
{firebaseApp &&
|
||||
<Box p={2}>
|
||||
<Box p={1}>
|
||||
<a href={`https://console.firebase.google.com/project/${firebaseApp.options.projectId}/authentication/providers`}
|
||||
rel="noopener noreferrer"
|
||||
target="_blank">
|
||||
<Button variant="outlined"
|
||||
<Button variant="text"
|
||||
color="primary">
|
||||
Open Firebase configuration
|
||||
</Button>
|
||||
</a>
|
||||
</Box>}
|
||||
</>;
|
||||
} else {
|
||||
} else if (!ignoredCodes.includes(authDelegate.authError.code)) {
|
||||
console.error(authDelegate.authError);
|
||||
errorView =
|
||||
<Box p={2}>
|
||||
{authDelegate.authError.message}
|
||||
<Box p={1}>
|
||||
<ErrorView error={authDelegate.authError.message}/>
|
||||
</Box>;
|
||||
}
|
||||
}
|
||||
@@ -126,45 +130,309 @@ export function FirebaseLoginView({
|
||||
if (authController.notAllowedError) {
|
||||
if (typeof authController.notAllowedError === "string") {
|
||||
notAllowedMessage = authController.notAllowedError;
|
||||
} else if (authController.notAllowedError instanceof Error) {
|
||||
notAllowedMessage = authController.notAllowedError.message;
|
||||
} else {
|
||||
notAllowedMessage = "It looks like you don't have access to the CMS, based on the specified Authenticator configuration";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<Grid
|
||||
container
|
||||
spacing={1}
|
||||
direction="column"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
style={{ minHeight: "100vh" }}
|
||||
>
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
minHeight: "100vh",
|
||||
p: 2
|
||||
}}>
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
width: "100%",
|
||||
maxWidth: 340
|
||||
}}>
|
||||
|
||||
<Box m={1}>
|
||||
{logoComponent}
|
||||
</Box>
|
||||
|
||||
<div id="firebase-ui"/>
|
||||
|
||||
{skipLoginButtonEnabled &&
|
||||
<Box m={2}>
|
||||
<Button onClick={authDelegate.skipLogin}>
|
||||
Skip login
|
||||
</Button>
|
||||
</Box>
|
||||
}
|
||||
|
||||
<Grid item xs={12}>
|
||||
<Box m={1}>
|
||||
{logoComponent}
|
||||
</Box>
|
||||
|
||||
{notAllowedMessage &&
|
||||
<Box p={2}>
|
||||
{notAllowedMessage}
|
||||
<ErrorView
|
||||
error={notAllowedMessage}/>
|
||||
</Box>}
|
||||
|
||||
{buildErrorView()}
|
||||
|
||||
</Grid>
|
||||
</Grid>
|
||||
{!passwordLoginSelected && <>
|
||||
|
||||
{buildOauthLoginButtons(authDelegate, resolvedSignInOptions, modeState.mode)}
|
||||
|
||||
{resolvedSignInOptions.includes("password") && <LoginButton
|
||||
text={"Email/password"}
|
||||
icon={<EmailIcon fontSize={"large"}/>}
|
||||
onClick={() => setPasswordLoginSelected(true)}/>}
|
||||
|
||||
|
||||
{resolvedSignInOptions.includes("anonymous") && <LoginButton
|
||||
text={"Log in anonymously"}
|
||||
icon={<PersonOutlineIcon fontSize={"large"}/>}
|
||||
onClick={authDelegate.anonymousLogin}/>}
|
||||
|
||||
{skipLoginButtonEnabled &&
|
||||
<Box m={1}>
|
||||
<Button onClick={authDelegate.skipLogin}>
|
||||
Skip login
|
||||
</Button>
|
||||
</Box>
|
||||
}
|
||||
|
||||
</>}
|
||||
|
||||
{passwordLoginSelected && <LoginForm
|
||||
authDelegate={authDelegate}
|
||||
onClose={() => setPasswordLoginSelected(false)}
|
||||
mode={modeState.mode}/>}
|
||||
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function LoginButton({
|
||||
icon,
|
||||
onClick,
|
||||
text
|
||||
}: { icon: React.ReactNode, onClick: () => void, text: string }) {
|
||||
return (
|
||||
<Box m={0.5} width={"100%"}>
|
||||
<Button fullWidth
|
||||
variant="outlined"
|
||||
onClick={onClick}>
|
||||
<Box sx={{
|
||||
p: '1',
|
||||
display: 'flex',
|
||||
width: "240px",
|
||||
height: "32px",
|
||||
alignItems: "center",
|
||||
justifyItems: "center"
|
||||
}}>
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
flexDirection: "column",
|
||||
width: "32px",
|
||||
alignItems: "center",
|
||||
justifyItems: "center"
|
||||
}}>
|
||||
{icon}
|
||||
</Box>
|
||||
<Box sx={{
|
||||
flexGrow: 1,
|
||||
pl: 2,
|
||||
textAlign: "center"
|
||||
}}>{text}</Box>
|
||||
</Box>
|
||||
</Button>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
function LoginForm({
|
||||
onClose,
|
||||
authDelegate,
|
||||
mode
|
||||
}: { onClose: () => void, authDelegate: FirebaseAuthDelegate, mode: "light" | "dark" }) {
|
||||
|
||||
const passwordRef = useRef<HTMLInputElement | null>(null);
|
||||
|
||||
const [email, setEmail] = useState<string>();
|
||||
const [availableProviders, setAvailableProviders] = useState<string[] | undefined>();
|
||||
const [password, setPassword] = useState<string>();
|
||||
|
||||
const shouldShowEmail = availableProviders === undefined;
|
||||
const loginMode = availableProviders && availableProviders.includes('password');
|
||||
const otherProvidersMode = availableProviders && !availableProviders.includes('password') && availableProviders.length > 0;
|
||||
const registrationMode = availableProviders && !availableProviders.includes('password');
|
||||
|
||||
useEffect(() => {
|
||||
console.log("loginMode", loginMode);
|
||||
if ((loginMode || registrationMode) && passwordRef.current) {
|
||||
passwordRef.current.focus()
|
||||
}
|
||||
}, [loginMode]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!document) return;
|
||||
const escFunction = (event: any) => {
|
||||
if (event.keyCode === 27) {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
document.addEventListener("keydown", escFunction, false);
|
||||
return () => {
|
||||
document.removeEventListener("keydown", escFunction, false);
|
||||
};
|
||||
}, [document]);
|
||||
|
||||
function handleEnterEmail() {
|
||||
if (email) {
|
||||
authDelegate.fetchSignInMethodsForEmail(email).then(setAvailableProviders);
|
||||
}
|
||||
}
|
||||
|
||||
function handleEnterPassword() {
|
||||
if (email && password) {
|
||||
authDelegate.emailPasswordLogin(email, password);
|
||||
}
|
||||
}
|
||||
|
||||
function handleRegistration() {
|
||||
if (email && password) {
|
||||
authDelegate.createUserWithEmailAndPassword(email, password);
|
||||
}
|
||||
}
|
||||
|
||||
const onBackPressed = () => {
|
||||
if (shouldShowEmail) {
|
||||
onClose();
|
||||
} else {
|
||||
setAvailableProviders(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmit = (event: any) => {
|
||||
event.preventDefault();
|
||||
if (shouldShowEmail) {
|
||||
handleEnterEmail();
|
||||
} else if (loginMode) {
|
||||
handleEnterPassword();
|
||||
} else if (registrationMode) {
|
||||
handleRegistration();
|
||||
}
|
||||
}
|
||||
|
||||
const label = registrationMode ? "No user found with that email. Pick a password to create a new account"
|
||||
: (loginMode ? "Please enter your password" : "Please enter your email");
|
||||
const button = registrationMode ? "Create account" : (loginMode ? "Login" : "Ok");
|
||||
|
||||
if (otherProvidersMode) {
|
||||
return (
|
||||
<Grid container spacing={1}>
|
||||
<Grid item xs={12}>
|
||||
<IconButton
|
||||
onClick={onBackPressed}>
|
||||
<ArrowBackIcon sx={{ width: 20, height: 20 }}/>
|
||||
</IconButton>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} sx={{ p: 1 }}>
|
||||
<Typography align={"center"} variant={"subtitle2"}>
|
||||
You already have an account
|
||||
</Typography>
|
||||
<Typography align={"center"} variant={"body2"}>
|
||||
You can use one of these
|
||||
methods to login with {email}
|
||||
</Typography>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}>
|
||||
{buildOauthLoginButtons(authDelegate, availableProviders, mode)}
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Grid container spacing={1}>
|
||||
<Grid item xs={12}>
|
||||
<IconButton
|
||||
onClick={onBackPressed}>
|
||||
<ArrowBackIcon sx={{ width: 20, height: 20 }}/>
|
||||
</IconButton>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} sx={{ p: 1 }}>
|
||||
<Typography align={"center"}
|
||||
variant={"subtitle2"}>{label}</Typography>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}
|
||||
sx={{ display: shouldShowEmail ? "inherit" : "none" }}>
|
||||
<TextField placeholder="Email" fullWidth autoFocus
|
||||
value={email}
|
||||
disabled={authDelegate.authLoading}
|
||||
type="email"
|
||||
onChange={(event) => setEmail(event.target.value)}/>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}
|
||||
sx={{ display: loginMode || registrationMode ? "inherit" : "none" }}>
|
||||
<TextField placeholder="Password" fullWidth
|
||||
value={password}
|
||||
disabled={authDelegate.authLoading}
|
||||
inputRef={passwordRef}
|
||||
type="password"
|
||||
onChange={(event) => setPassword(event.target.value)}/>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}>
|
||||
<Box sx={{
|
||||
display: "flex",
|
||||
justifyContent: "end",
|
||||
alignItems: "center",
|
||||
width: "100%"
|
||||
}}>
|
||||
|
||||
{authDelegate.authLoading &&
|
||||
<CircularProgress sx={{ p: 1 }} size={16}
|
||||
thickness={8}/>
|
||||
}
|
||||
|
||||
<Button type="submit">
|
||||
{button}
|
||||
</Button>
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</form>
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
function buildOauthLoginButtons(authDelegate: FirebaseAuthDelegate, providers: string[], mode: "light" | "dark") {
|
||||
return <>
|
||||
{providers.includes("google.com") && <LoginButton
|
||||
text={"Sign in with Google"}
|
||||
icon={googleIcon(mode)}
|
||||
onClick={authDelegate.googleLogin}/>}
|
||||
|
||||
{providers.includes("microsoft.com") && <LoginButton
|
||||
text={"Sign in with Microsoft"}
|
||||
icon={microsoftIcon(mode)}
|
||||
onClick={authDelegate.microsoftLogin}/>}
|
||||
|
||||
{providers.includes("apple.com") && <LoginButton
|
||||
text={"Sign in with Apple"}
|
||||
icon={appleIcon(mode)}
|
||||
onClick={authDelegate.appleLogin}/>}
|
||||
|
||||
{providers.includes("github.com") && <LoginButton
|
||||
text={"Sign in with Github"}
|
||||
icon={githubIcon(mode)}
|
||||
onClick={authDelegate.githubLogin}/>}
|
||||
|
||||
{providers.includes("facebook.com") && <LoginButton
|
||||
text={"Sign in with Facebook"}
|
||||
icon={facebookIcon(mode)}
|
||||
onClick={authDelegate.facebookLogin}/>}
|
||||
|
||||
{providers.includes("twitter.com") && <LoginButton
|
||||
text={"Sign in with Twitter"}
|
||||
icon={twitterIcon(mode)}
|
||||
onClick={authDelegate.twitterLogin}/>}
|
||||
</>
|
||||
}
|
||||
|
||||
138
src/firebase_app/components/social_icons.tsx
Normal file
138
src/firebase_app/components/social_icons.tsx
Normal file
@@ -0,0 +1,138 @@
|
||||
export const googleIcon = (mode: "light" | "dark") => <>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 64 64"
|
||||
width={32}
|
||||
height={32}
|
||||
>
|
||||
<linearGradient
|
||||
id="95yY7w43Oj6n2vH63j6HJb"
|
||||
x1="29.401"
|
||||
x2="29.401"
|
||||
y1="4.064"
|
||||
y2="106.734"
|
||||
gradientTransform="matrix(1 0 0 -1 0 66)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop offset="0" stopColor="#ff5840"/>
|
||||
<stop offset=".007" stopColor="#ff5840"/>
|
||||
<stop offset=".989" stopColor="#fa528c"/>
|
||||
<stop offset="1" stopColor="#fa528c"/>
|
||||
</linearGradient>
|
||||
<path
|
||||
fill="url(#95yY7w43Oj6n2vH63j6HJb)"
|
||||
d="M47.46,15.5l-1.37,1.48c-1.34,1.44-3.5,1.67-5.15,0.6c-2.71-1.75-6.43-3.13-11-2.37 c-4.94,0.83-9.17,3.85-11.64,
|
||||
7.97l-8.03-6.08C14.99,9.82,23.2,5,32.5,5c5,0,9.94,1.56,14.27,4.46 C48.81,10.83,49.13,13.71,47.46,15.5z"
|
||||
/>
|
||||
<linearGradient
|
||||
id="95yY7w43Oj6n2vH63j6HJc"
|
||||
x1="12.148"
|
||||
x2="12.148"
|
||||
y1=".872"
|
||||
y2="47.812"
|
||||
gradientTransform="matrix(1 0 0 -1 0 66)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop offset="0" stopColor="#feaa53"/>
|
||||
<stop offset=".612" stopColor="#ffcd49"/>
|
||||
<stop offset="1" stopColor="#ffde44"/>
|
||||
</linearGradient>
|
||||
<path
|
||||
fill="url(#95yY7w43Oj6n2vH63j6HJc)"
|
||||
d="M16.01,30.91c-0.09,2.47,0.37,4.83,1.27,6.96l-8.21,6.05c-1.35-2.51-2.3-5.28-2.75-8.22 c-1.06-6.88,0.54-13.38,
|
||||
3.95-18.6l8.03,6.08C16.93,25.47,16.1,28.11,16.01,30.91z"
|
||||
/>
|
||||
<linearGradient
|
||||
id="95yY7w43Oj6n2vH63j6HJd"
|
||||
x1="29.76"
|
||||
x2="29.76"
|
||||
y1="32.149"
|
||||
y2="-6.939"
|
||||
gradientTransform="matrix(1 0 0 -1 0 66)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop offset="0" stopColor="#42d778"/>
|
||||
<stop offset=".428" stopColor="#3dca76"/>
|
||||
<stop offset="1" stopColor="#34b171"/>
|
||||
</linearGradient>
|
||||
<path
|
||||
fill="url(#95yY7w43Oj6n2vH63j6HJd)"
|
||||
d="M50.45,51.28c-4.55,4.07-10.61,6.57-17.36,6.71C22.91,58.2,13.66,52.53,9.07,43.92l8.21-6.05 C19.78,43.81,
|
||||
25.67,48,32.5,48c3.94,0,7.52-1.28,10.33-3.44L50.45,51.28z"
|
||||
/>
|
||||
<linearGradient
|
||||
id="95yY7w43Oj6n2vH63j6HJe"
|
||||
x1="46"
|
||||
x2="46"
|
||||
y1="3.638"
|
||||
y2="35.593"
|
||||
gradientTransform="matrix(1 0 0 -1 0 66)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop offset="0" stopColor="#155cde"/>
|
||||
<stop offset=".278" stopColor="#1f7fe5"/>
|
||||
<stop offset=".569" stopColor="#279ceb"/>
|
||||
<stop offset=".82" stopColor="#2cafef"/>
|
||||
<stop offset="1" stopColor="#2eb5f0"/>
|
||||
</linearGradient>
|
||||
<path
|
||||
fill="url(#95yY7w43Oj6n2vH63j6HJe)"
|
||||
d="M59,31.97c0.01,7.73-3.26,14.58-8.55,19.31l-7.62-6.72c2.1-1.61,3.77-3.71,4.84-6.15
|
||||
c0.29-0.66-0.2-1.41-0.92-1.41H37c-2.21,0-4-1.79-4-4v-2c0-2.21,1.79-4,4-4h17C56.75,27,59,29.22,59,31.97z"
|
||||
/>
|
||||
</svg>
|
||||
</>;
|
||||
|
||||
export const appleIcon = (mode: "light" | "dark") => <svg width={32} height={32}
|
||||
viewBox="0 0 56 56"
|
||||
style={{ transform: 'scale(2.8)' }}
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<g stroke={mode === "light" ? "#424245" : "white"} strokeWidth="0.5"
|
||||
fillRule="evenodd">
|
||||
<path
|
||||
d="M28.2226562,20.3846154 C29.0546875,20.3846154 30.0976562,19.8048315 30.71875,19.0317864 C31.28125,18.3312142 31.6914062,17.352829 31.6914062,16.3744437 C31.6914062,16.2415766 31.6796875,16.1087095 31.65625,16 C30.7304687,16.0362365 29.6171875,16.640178 28.9492187,17.4494596 C28.421875,18.06548 27.9414062,19.0317864 27.9414062,20.0222505 C27.9414062,20.1671964 27.9648438,20.3121424 27.9765625,20.3604577 C28.0351562,20.3725366 28.1289062,20.3846154 28.2226562,20.3846154 Z M25.2929688,35 C26.4296875,35 26.9335938,34.214876 28.3515625,34.214876 C29.7929688,34.214876 30.109375,34.9758423 31.375,34.9758423 C32.6171875,34.9758423 33.4492188,33.792117 34.234375,32.6325493 C35.1132812,31.3038779 35.4765625,29.9993643 35.5,29.9389701 C35.4179688,29.9148125 33.0390625,28.9122695 33.0390625,26.0979021 C33.0390625,23.6579784 34.9140625,22.5588048 35.0195312,22.474253 C33.7773438,20.6382708 31.890625,20.5899555 31.375,20.5899555 C29.9804688,20.5899555 28.84375,21.4596313 28.1289062,21.4596313 C27.3554688,21.4596313 26.3359375,20.6382708 25.1289062,20.6382708 C22.8320312,20.6382708 20.5,22.5950413 20.5,26.2911634 C20.5,28.5861411 21.3671875,31.013986 22.4335938,32.5842339 C23.3476562,33.9129053 24.1445312,35 25.2929688,35 Z"
|
||||
fill={mode === "light" ? "#424245" : "white"} fillRule="nonzero"/>
|
||||
</g>
|
||||
</svg>;
|
||||
|
||||
export const githubIcon = (mode: "light" | "dark") => <svg
|
||||
fill={mode === "light" ? "#1c1e21" : "white"}
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
width={28}
|
||||
height={28}
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/>
|
||||
</svg>;
|
||||
|
||||
|
||||
export const facebookIcon = (mode: "light" | "dark") => <svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={28} height={28}
|
||||
viewBox="0 0 90 90">
|
||||
<g>
|
||||
<path
|
||||
d="M90,15.001C90,7.119,82.884,0,75,0H15C7.116,0,0,7.119,0,15.001v59.998 C0,82.881,7.116,90,15.001,90H45V56H34V41h11v-5.844C45,25.077,52.568,16,61.875,16H74v15H61.875C60.548,31,59,32.611,59,35.024V41 h15v15H59v34h16c7.884,0,15-7.119,15-15.001V15.001z"
|
||||
fill={mode === "light" ? "#39569c" : "white"}/>
|
||||
</g>
|
||||
</svg>;
|
||||
|
||||
export const microsoftIcon = (mode: "light" | "dark") => <svg
|
||||
xmlns="http://www.w3.org/2000/svg" width={28} height={28}
|
||||
viewBox="0 0 480 480">
|
||||
<g>
|
||||
<path
|
||||
d="M0.176,224L0.001,67.963l192-26.072V224H0.176z M224.001,37.241L479.937,0v224H224.001V37.241z M479.999,256l-0.062,224 l-255.936-36.008V256H479.999z M192.001,439.918L0.157,413.621L0.147,256h191.854V439.918z"
|
||||
fill={mode === "light" ? "#00a2ed" : "white"}/>
|
||||
</g>
|
||||
</svg>;
|
||||
|
||||
export const twitterIcon = (mode: "light" | "dark") => <svg
|
||||
xmlns="http://www.w3.org/2000/svg" width={28} height={28}
|
||||
viewBox="0 0 24 24">
|
||||
<path fill={mode === "light" ? "#00acee" : "white"}
|
||||
d="M24 4.557c-.883.392-1.832.656-2.828.775 1.017-.609 1.798-1.574 2.165-2.724-.951.564-2.005.974-3.127 1.195-.897-.957-2.178-1.555-3.594-1.555-3.179 0-5.515 2.966-4.797 6.045-4.091-.205-7.719-2.165-10.148-5.144-1.29 2.213-.669 5.108 1.523 6.574-.806-.026-1.566-.247-2.229-.616-.054 2.281 1.581 4.415 3.949 4.89-.693.188-1.452.232-2.224.084.626 1.956 2.444 3.379 4.6 3.419-2.07 1.623-4.678 2.348-7.29 2.04 2.179 1.397 4.768 2.212 7.548 2.212 9.142 0 14.307-7.721 13.995-14.646.962-.695 1.797-1.562 2.457-2.549z"/>
|
||||
</svg>;
|
||||
|
||||
@@ -1,16 +1,36 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import {
|
||||
Auth,
|
||||
FacebookAuthProvider,
|
||||
fetchSignInMethodsForEmail,
|
||||
getAuth,
|
||||
GoogleAuthProvider,
|
||||
OAuthProvider,
|
||||
onAuthStateChanged,
|
||||
signInWithEmailAndPassword,
|
||||
signInWithPopup,
|
||||
signOut,
|
||||
User as FirebaseUser
|
||||
} from "firebase/auth";
|
||||
import { FirebaseApp } from "firebase/app";
|
||||
import { AuthDelegate } from "../../models";
|
||||
import {
|
||||
FirebaseAuthDelegate,
|
||||
FirebaseSignInOption,
|
||||
FirebaseSignInProvider
|
||||
} from "../models/auth";
|
||||
import firebase from "firebase/compat";
|
||||
import {
|
||||
createUserWithEmailAndPassword,
|
||||
signInAnonymously
|
||||
} from "@firebase/auth";
|
||||
import GithubAuthProvider = firebase.auth.GithubAuthProvider;
|
||||
import TwitterAuthProvider = firebase.auth.TwitterAuthProvider;
|
||||
|
||||
interface FirebaseAuthHandlerProps {
|
||||
firebaseApp?: FirebaseApp,
|
||||
firebaseApp?: FirebaseApp;
|
||||
signInOptions: Array<FirebaseSignInProvider | FirebaseSignInOption>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -19,11 +39,13 @@ interface FirebaseAuthHandlerProps {
|
||||
*/
|
||||
export const useFirebaseAuthDelegate = (
|
||||
{
|
||||
firebaseApp
|
||||
}: FirebaseAuthHandlerProps): AuthDelegate<FirebaseUser> => {
|
||||
firebaseApp,
|
||||
signInOptions
|
||||
}: FirebaseAuthHandlerProps): FirebaseAuthDelegate => {
|
||||
|
||||
const [loggedUser, setLoggedUser] = useState<FirebaseUser | null>(null);
|
||||
const [loggedUser, setLoggedUser] = useState<FirebaseUser | null | undefined>(undefined); // logged user, anonymous or logged out
|
||||
const [authProviderError, setAuthProviderError] = useState<any>();
|
||||
const [initialLoading, setInitialLoading] = useState(true);
|
||||
const [authLoading, setAuthLoading] = useState(true);
|
||||
const [loginSkipped, setLoginSkipped] = useState<boolean>(false);
|
||||
|
||||
@@ -35,6 +57,7 @@ export const useFirebaseAuthDelegate = (
|
||||
useEffect(() => {
|
||||
if (!firebaseApp) return;
|
||||
const auth = getAuth(firebaseApp);
|
||||
setLoggedUser(auth.currentUser)
|
||||
return onAuthStateChanged(
|
||||
auth,
|
||||
updateFirebaseUser,
|
||||
@@ -44,6 +67,7 @@ export const useFirebaseAuthDelegate = (
|
||||
|
||||
const updateFirebaseUser = async (firebaseUser: FirebaseUser | null) => {
|
||||
setLoggedUser(firebaseUser);
|
||||
setInitialLoading(false);
|
||||
setAuthLoading(false);
|
||||
};
|
||||
|
||||
@@ -54,14 +78,140 @@ export const useFirebaseAuthDelegate = (
|
||||
setLoggedUser(null);
|
||||
setAuthProviderError(null);
|
||||
});
|
||||
setLoginSkipped(false);
|
||||
}
|
||||
|
||||
const getProviderOptions = (providerId: FirebaseSignInProvider): FirebaseSignInOption | undefined => {
|
||||
return signInOptions.find((option) => {
|
||||
if (option === null) throw Error("useFirebaseAuthDelegate");
|
||||
if (typeof option === "object" && option.provider === providerId)
|
||||
return option as FirebaseSignInOption;
|
||||
return undefined;
|
||||
}) as FirebaseSignInOption | undefined;
|
||||
}
|
||||
|
||||
const googleLogin = () => {
|
||||
const provider = new GoogleAuthProvider();
|
||||
const options = getProviderOptions("google.com");
|
||||
if (options?.scopes)
|
||||
options.scopes.forEach((scope) => provider.addScope(scope));
|
||||
if (options?.customParameters)
|
||||
provider.setCustomParameters(options.customParameters);
|
||||
const auth = getAuth();
|
||||
signInWithPopup(auth, provider).catch(setAuthProviderError);
|
||||
}
|
||||
|
||||
function doOauthLogin(auth: Auth, provider: OAuthProvider | FacebookAuthProvider | GithubAuthProvider | TwitterAuthProvider) {
|
||||
setAuthLoading(true);
|
||||
signInWithPopup(auth, provider)
|
||||
.catch(setAuthProviderError)
|
||||
.then(() => setAuthLoading(false));
|
||||
}
|
||||
|
||||
const anonymousLogin = () => {
|
||||
const auth = getAuth();
|
||||
setAuthLoading(true);
|
||||
signInAnonymously(auth)
|
||||
.catch(setAuthProviderError)
|
||||
.then(() => setAuthLoading(false));
|
||||
}
|
||||
|
||||
const appleLogin = () => {
|
||||
const provider = new OAuthProvider('apple.com');
|
||||
const options = getProviderOptions('apple.com');
|
||||
if (options?.scopes)
|
||||
options.scopes.forEach((scope) => provider.addScope(scope));
|
||||
if (options?.customParameters)
|
||||
provider.setCustomParameters(options.customParameters);
|
||||
const auth = getAuth();
|
||||
doOauthLogin(auth, provider);
|
||||
}
|
||||
|
||||
const facebookLogin = () => {
|
||||
const provider = new FacebookAuthProvider();
|
||||
const options = getProviderOptions('facebook.com');
|
||||
if (options?.scopes)
|
||||
options.scopes.forEach((scope) => provider.addScope(scope));
|
||||
if (options?.customParameters)
|
||||
provider.setCustomParameters(options.customParameters);
|
||||
const auth = getAuth();
|
||||
doOauthLogin(auth, provider);
|
||||
}
|
||||
|
||||
const githubLogin = () => {
|
||||
const provider = new GithubAuthProvider();
|
||||
const options = getProviderOptions('github.com');
|
||||
if (options?.scopes)
|
||||
options.scopes.forEach((scope) => provider.addScope(scope));
|
||||
if (options?.customParameters)
|
||||
provider.setCustomParameters(options.customParameters);
|
||||
const auth = getAuth();
|
||||
doOauthLogin(auth, provider);
|
||||
}
|
||||
|
||||
const microsoftLogin = () => {
|
||||
const provider = new OAuthProvider('microsoft.com');
|
||||
const options = getProviderOptions('microsoft.com');
|
||||
if (options?.scopes)
|
||||
options.scopes.forEach((scope) => provider.addScope(scope));
|
||||
if (options?.customParameters)
|
||||
provider.setCustomParameters(options.customParameters);
|
||||
const auth = getAuth();
|
||||
doOauthLogin(auth, provider);
|
||||
}
|
||||
|
||||
const twitterLogin = () => {
|
||||
const provider = new TwitterAuthProvider();
|
||||
const options = getProviderOptions('twitter.com');
|
||||
if (options?.customParameters)
|
||||
provider.setCustomParameters(options.customParameters);
|
||||
const auth = getAuth();
|
||||
doOauthLogin(auth, provider);
|
||||
}
|
||||
|
||||
const emailPasswordLogin = (email: string, password: string) => {
|
||||
const auth = getAuth();
|
||||
setAuthLoading(true);
|
||||
signInWithEmailAndPassword(auth, email, password)
|
||||
.catch(setAuthProviderError)
|
||||
.then(() => setAuthLoading(false));
|
||||
}
|
||||
|
||||
const registerWithPasswordEmail = (email: string, password: string) => {
|
||||
const auth = getAuth();
|
||||
setAuthLoading(true);
|
||||
createUserWithEmailAndPassword(auth, email, password)
|
||||
.catch(setAuthProviderError)
|
||||
.then(() => setAuthLoading(false));
|
||||
}
|
||||
|
||||
const getSignInMethodsForEmail = (email: string): Promise<string[]> => {
|
||||
const auth = getAuth();
|
||||
setAuthLoading(true);
|
||||
return fetchSignInMethodsForEmail(auth, email)
|
||||
.then((res) => {
|
||||
setAuthLoading(false);
|
||||
return res;
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
user: loggedUser,
|
||||
user: loggedUser ?? null,
|
||||
authError: authProviderError,
|
||||
authLoading,
|
||||
initialLoading,
|
||||
signOut: onSignOut,
|
||||
loginSkipped,
|
||||
skipLogin
|
||||
skipLogin,
|
||||
googleLogin,
|
||||
anonymousLogin,
|
||||
appleLogin,
|
||||
facebookLogin,
|
||||
githubLogin,
|
||||
microsoftLogin,
|
||||
twitterLogin,
|
||||
emailPasswordLogin,
|
||||
fetchSignInMethodsForEmail: getSignInMethodsForEmail,
|
||||
createUserWithEmailAndPassword: registerWithPasswordEmail
|
||||
};
|
||||
};
|
||||
|
||||
@@ -25,5 +25,7 @@ export {
|
||||
export type { InitialiseFirebaseResult } from "./hooks/useInitialiseFirebase";
|
||||
export { useInitialiseFirebase } from "./hooks/useInitialiseFirebase";
|
||||
|
||||
export type { FirebaseAuthDelegate } from "./models/auth";
|
||||
|
||||
export type { FirestoreTextSearchController } from "./models/text_search";
|
||||
export { performAlgoliaTextSearch } from "./models/text_search";
|
||||
|
||||
69
src/firebase_app/models/auth.tsx
Normal file
69
src/firebase_app/models/auth.tsx
Normal file
@@ -0,0 +1,69 @@
|
||||
import { User as FirebaseUser } from "firebase/auth";
|
||||
|
||||
import { AuthDelegate } from "../../models";
|
||||
|
||||
|
||||
/**
|
||||
* @category Firebase
|
||||
*/
|
||||
export type FirebaseSignInProvider =
|
||||
// | 'email'
|
||||
| 'password'
|
||||
// | 'phone'
|
||||
| 'anonymous'
|
||||
| 'google.com'
|
||||
| 'facebook.com'
|
||||
| 'github.com'
|
||||
| 'twitter.com'
|
||||
| 'microsoft.com'
|
||||
| 'apple.com';
|
||||
|
||||
|
||||
/**
|
||||
* @category Firebase
|
||||
*/
|
||||
export type FirebaseSignInOption = {
|
||||
provider: FirebaseSignInProvider;
|
||||
scopes?: string[];
|
||||
customParameters?: Record<string, string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* @category Firebase
|
||||
*/
|
||||
export type FirebaseAuthDelegate =
|
||||
AuthDelegate<FirebaseUser> & {
|
||||
|
||||
authLoading: boolean;
|
||||
|
||||
googleLogin: () => void;
|
||||
|
||||
anonymousLogin: () => void;
|
||||
|
||||
appleLogin: () => void;
|
||||
|
||||
facebookLogin: () => void;
|
||||
|
||||
githubLogin: () => void;
|
||||
|
||||
microsoftLogin: () => void;
|
||||
|
||||
twitterLogin: () => void;
|
||||
|
||||
emailPasswordLogin: (email: string, password: string) => void;
|
||||
|
||||
fetchSignInMethodsForEmail: (email: string) => Promise<string[]>;
|
||||
|
||||
createUserWithEmailAndPassword: (email: string, password: string) => void;
|
||||
|
||||
/**
|
||||
* Has the user skipped the login process
|
||||
*/
|
||||
loginSkipped?: boolean;
|
||||
|
||||
/**
|
||||
* Skip login
|
||||
*/
|
||||
skipLogin?: () => void;
|
||||
|
||||
};
|
||||
@@ -25,6 +25,19 @@ export interface AuthController<UserType extends User = User> {
|
||||
*/
|
||||
canAccessMainView: boolean;
|
||||
|
||||
/**
|
||||
* Initial loading flag. It is used not to display the login screen
|
||||
* when the app first loads and it has not been checked whether the user
|
||||
* is logged in or not.
|
||||
*/
|
||||
initialLoading: boolean;
|
||||
|
||||
/**
|
||||
* If you have defined an {@link Authenticator}, this flag will be set to
|
||||
* true while it loads
|
||||
*/
|
||||
authLoading: boolean;
|
||||
|
||||
/**
|
||||
* The current user was not allowed access
|
||||
*/
|
||||
@@ -79,9 +92,11 @@ export type AuthDelegate<UserType extends User = User> = {
|
||||
authError?: any;
|
||||
|
||||
/**
|
||||
* Is the login process ongoing
|
||||
* Initial loading flag. It is used not to display the login screen
|
||||
* when the app first loads and it has not been checked whether the user
|
||||
* is logged in or not.
|
||||
*/
|
||||
authLoading: boolean;
|
||||
initialLoading?: boolean;
|
||||
|
||||
/**
|
||||
* Sign out
|
||||
|
||||
@@ -287,6 +287,11 @@ export default function App() {
|
||||
user,
|
||||
authController
|
||||
}) => {
|
||||
// You can throw an error to display a message
|
||||
if(user?.email?.includes("flanders")){
|
||||
throw Error("Stupid Flanders!");
|
||||
}
|
||||
|
||||
console.log("Allowing access to", user?.email);
|
||||
// This is an example of retrieving async data related to the user
|
||||
// and storing it in the user extra field.
|
||||
|
||||
@@ -6875,10 +6875,10 @@ firebase@^9.4.1:
|
||||
"@firebase/storage-compat" "0.1.8"
|
||||
"@firebase/util" "1.4.2"
|
||||
|
||||
firebaseui@~0.600:
|
||||
version "0.600.0"
|
||||
resolved "https://registry.yarnpkg.com/firebaseui/-/firebaseui-0.600.0.tgz#4d9b1ab978e86d23f306952fa347ffdb7ebac510"
|
||||
integrity sha512-5iaRcuaaXTmCIU+VNJHIerzz1bgcfGmPTZCFL/yLHsQ6o3KeUggr/mY2rmMh2aMT38UgpyV5QndwEAZDiOeOWg==
|
||||
firebaseui@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/firebaseui/-/firebaseui-6.0.0.tgz#074580e9e7da93ed3d551a869dbab4e0902701d7"
|
||||
integrity sha512-fhzteivWRTnOhSk5JkIVLmcXk6aVh4ZtW+KAnJijeeZhAyM+Ed9m1YgC4l+VK+NOp+kerVw6HL1mRdvDF6Yeuw==
|
||||
dependencies:
|
||||
dialog-polyfill "^0.4.7"
|
||||
material-design-lite "^1.2.0"
|
||||
|
||||
Reference in New Issue
Block a user