Merge pull request 'RC-4-connect-firebase-auth' (#2) from RC-4-connect-firebase-auth into dev
Reviewed-on: #2
This commit is contained in:
@@ -7,7 +7,7 @@ const aliases = (prefix = `src`) => ({
|
||||
'app/shared-components': `${prefix}/app/shared-components`,
|
||||
'app/configs': `${prefix}/app/configs`,
|
||||
'app/theme-layouts': `${prefix}/app/theme-layouts`,
|
||||
'app/AppContext': `${prefix}/app/AppContext`,
|
||||
'app/contexts/AppContext': `${prefix}/app/contexts/AppContext`,
|
||||
});
|
||||
|
||||
module.exports = aliases;
|
||||
|
||||
@@ -1,16 +1,34 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"@fuse/*": ["./src/@fuse/*"],
|
||||
"@history*": ["./src/@history"],
|
||||
"@lodash": ["./src/@lodash"],
|
||||
"@mock-api": ["./src/@mock-api"],
|
||||
"app/store/*": ["./src/app/store/*"],
|
||||
"app/shared-components/*": ["./src/app/shared-components/*"],
|
||||
"app/configs/*": ["./src/app/configs/*"],
|
||||
"app/theme-layouts/*": ["./src/app/theme-layouts/*"],
|
||||
"app/AppContext": ["./src/app/AppContext"]
|
||||
}
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"@fuse/*": [
|
||||
"./src/@fuse/*"
|
||||
],
|
||||
"@history*": [
|
||||
"./src/@history"
|
||||
],
|
||||
"@lodash": [
|
||||
"./src/@lodash"
|
||||
],
|
||||
"@mock-api": [
|
||||
"./src/@mock-api"
|
||||
],
|
||||
"app/store/*": [
|
||||
"./src/app/store/*"
|
||||
],
|
||||
"app/shared-components/*": [
|
||||
"./src/app/shared-components/*"
|
||||
],
|
||||
"app/configs/*": [
|
||||
"./src/app/configs/*"
|
||||
],
|
||||
"app/theme-layouts/*": [
|
||||
"./src/app/theme-layouts/*"
|
||||
],
|
||||
"app/contexts/AppContext": [
|
||||
"./src/app/contexts/AppContext"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import FuseUtils from '@fuse/utils';
|
||||
import AppContext from 'app/AppContext';
|
||||
import AppContext from 'src/app/contexts/AppContext';
|
||||
import { Component } from 'react';
|
||||
import { matchRoutes } from 'react-router-dom';
|
||||
import withRouter from '@fuse/core/withRouter';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useDeepCompareEffect } from '@fuse/hooks';
|
||||
import _ from '@lodash';
|
||||
import AppContext from 'app/AppContext';
|
||||
import AppContext from 'src/app/contexts/AppContext';
|
||||
import {
|
||||
generateSettings,
|
||||
selectFuseCurrentSettings,
|
||||
|
||||
@@ -14,7 +14,7 @@ import { selectMainTheme } from 'app/store/fuse/settingsSlice';
|
||||
import FuseAuthorization from '@fuse/core/FuseAuthorization';
|
||||
import settingsConfig from 'app/configs/settingsConfig';
|
||||
import withAppProviders from './withAppProviders';
|
||||
import { AuthProvider } from './auth/AuthContext';
|
||||
import { AuthProvider } from './contexts/AuthContext';
|
||||
|
||||
// import axios from 'axios';
|
||||
/**
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export { default as authRoles } from './authRoles';
|
||||
@@ -1,3 +0,0 @@
|
||||
import JwtService from './jwtService';
|
||||
|
||||
export default JwtService;
|
||||
@@ -1,151 +0,0 @@
|
||||
import FuseUtils from '@fuse/utils/FuseUtils';
|
||||
import axios from 'axios';
|
||||
import jwtDecode from 'jwt-decode';
|
||||
import jwtServiceConfig from './jwtServiceConfig';
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
|
||||
class JwtService extends FuseUtils.EventEmitter {
|
||||
init() {
|
||||
this.setInterceptors();
|
||||
this.handleAuthentication();
|
||||
}
|
||||
|
||||
setInterceptors = () => {
|
||||
axios.interceptors.response.use(
|
||||
(response) => {
|
||||
return response;
|
||||
},
|
||||
(err) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (err.response.status === 401 && err.config && !err.config.__isRetryRequest) {
|
||||
// if you ever get an unauthorized response, logout the user
|
||||
this.emit('onAutoLogout', 'Invalid access_token');
|
||||
this.setSession(null);
|
||||
}
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
handleAuthentication = () => {
|
||||
const access_token = this.getAccessToken();
|
||||
|
||||
if (!access_token) {
|
||||
this.emit('onNoAccessToken');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isAuthTokenValid(access_token)) {
|
||||
this.setSession(access_token);
|
||||
this.emit('onAutoLogin', true);
|
||||
} else {
|
||||
this.setSession(null);
|
||||
this.emit('onAutoLogout', 'access_token expired');
|
||||
}
|
||||
};
|
||||
|
||||
createUser = (data) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios.post(jwtServiceConfig.signUp, data).then((response) => {
|
||||
if (response.data.user) {
|
||||
this.setSession(response.data.access_token);
|
||||
resolve(response.data.user);
|
||||
this.emit('onLogin', response.data.user);
|
||||
} else {
|
||||
reject(response.data.error);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
signInWithEmailAndPassword = (email, password) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios
|
||||
.get(jwtServiceConfig.signIn, {
|
||||
data: {
|
||||
email,
|
||||
password,
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.data.user) {
|
||||
this.setSession(response.data.access_token);
|
||||
resolve(response.data.user);
|
||||
this.emit('onLogin', response.data.user);
|
||||
} else {
|
||||
reject(response.data.error);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
signInWithToken = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios
|
||||
.get(jwtServiceConfig.accessToken, {
|
||||
data: {
|
||||
access_token: this.getAccessToken(),
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.data.user) {
|
||||
this.setSession(response.data.access_token);
|
||||
resolve(response.data.user);
|
||||
} else {
|
||||
this.logout();
|
||||
reject(new Error('Failed to login with token.'));
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
this.logout();
|
||||
reject(new Error('Failed to login with token.'));
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
updateUserData = (user) => {
|
||||
return axios.post(jwtServiceConfig.updateUser, {
|
||||
user,
|
||||
});
|
||||
};
|
||||
|
||||
setSession = (access_token) => {
|
||||
if (access_token) {
|
||||
localStorage.setItem('jwt_access_token', access_token);
|
||||
axios.defaults.headers.common.Authorization = `Bearer ${access_token}`;
|
||||
} else {
|
||||
localStorage.removeItem('jwt_access_token');
|
||||
delete axios.defaults.headers.common.Authorization;
|
||||
}
|
||||
};
|
||||
|
||||
logout = () => {
|
||||
this.setSession(null);
|
||||
this.emit('onLogout', 'Logged out');
|
||||
};
|
||||
|
||||
isAuthTokenValid = (access_token) => {
|
||||
if (!access_token) {
|
||||
return false;
|
||||
}
|
||||
const decoded = jwtDecode(access_token);
|
||||
const currentTime = Date.now() / 1000;
|
||||
if (decoded.exp < currentTime) {
|
||||
console.warn('access token expired');
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
getAccessToken = () => {
|
||||
return window.localStorage.getItem('jwt_access_token');
|
||||
};
|
||||
}
|
||||
|
||||
const instance = new JwtService();
|
||||
|
||||
export default instance;
|
||||
@@ -1,8 +0,0 @@
|
||||
const jwtServiceConfig = {
|
||||
signIn: 'api/auth/sign-in',
|
||||
signUp: 'api/auth/sign-up',
|
||||
accessToken: 'api/auth/access-token',
|
||||
updateUser: 'api/auth/user/update',
|
||||
};
|
||||
|
||||
export default jwtServiceConfig;
|
||||
@@ -19,7 +19,7 @@ const settingsConfig = {
|
||||
To make whole app accessible without authorization by default set defaultAuth: null
|
||||
*** The individual route configs which has auth option won't be overridden.
|
||||
*/
|
||||
defaultAuth: ['admin'],
|
||||
defaultAuth: ['admin', 'staff', 'user'],
|
||||
/*
|
||||
Default redirect url for the logged-in user,
|
||||
*/
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useDispatch } from 'react-redux';
|
||||
import FuseSplashScreen from '@fuse/core/FuseSplashScreen';
|
||||
import { showMessage } from 'app/store/fuse/messageSlice';
|
||||
import { logoutUser, setUser } from 'app/store/userSlice';
|
||||
import jwtService from './services/jwtService';
|
||||
import { authService, firebase } from '../services';
|
||||
|
||||
const AuthContext = React.createContext();
|
||||
|
||||
@@ -14,44 +14,35 @@ function AuthProvider({ children }) {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
jwtService.on('onAutoLogin', () => {
|
||||
dispatch(showMessage({ message: 'Signing in with JWT' }));
|
||||
|
||||
/**
|
||||
* Sign in and retrieve user data with stored token
|
||||
*/
|
||||
jwtService
|
||||
.signInWithToken()
|
||||
.then((user) => {
|
||||
success(user, 'Signed in with JWT');
|
||||
})
|
||||
.catch((error) => {
|
||||
pass(error.message);
|
||||
});
|
||||
});
|
||||
|
||||
jwtService.on('onLogin', (user) => {
|
||||
success(user, 'Signed in');
|
||||
});
|
||||
|
||||
jwtService.on('onLogout', () => {
|
||||
authService.on('onLogout', () => {
|
||||
pass('Signed out');
|
||||
|
||||
dispatch(logoutUser());
|
||||
});
|
||||
|
||||
jwtService.on('onAutoLogout', (message) => {
|
||||
pass(message);
|
||||
authService.init(firebase.auth, firebase.db);
|
||||
|
||||
dispatch(logoutUser());
|
||||
authService.onAuthStateChanged((authUser) => {
|
||||
dispatch(showMessage({ message: 'Signing...' }));
|
||||
if (authUser) {
|
||||
authService
|
||||
.getUserData(authUser.uid)
|
||||
.then((user) => {
|
||||
if (user) {
|
||||
success(user, 'Signed in');
|
||||
} else {
|
||||
const { displayName, photoURL, email } = authUser;
|
||||
success({ role: 'user', data: { displayName, photoURL, email } }, 'Signed in');
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
pass(error.message);
|
||||
});
|
||||
} else {
|
||||
pass('Signed out');
|
||||
}
|
||||
});
|
||||
|
||||
jwtService.on('onNoAccessToken', () => {
|
||||
pass();
|
||||
});
|
||||
|
||||
jwtService.init();
|
||||
|
||||
function success(user, message) {
|
||||
if (message) {
|
||||
dispatch(showMessage({ message }));
|
||||
@@ -1,7 +1,7 @@
|
||||
import i18next from 'i18next';
|
||||
|
||||
import ForgotPasswordPage from './ForgotPasswordPage';
|
||||
import authRoles from '../../../auth/authRoles';
|
||||
import authRoles from '../../../configs/authRoles';
|
||||
import en from './i18n/en';
|
||||
|
||||
i18next.addResourceBundle('en', 'forgotPasswordPage', en);
|
||||
|
||||
@@ -7,6 +7,7 @@ import Typography from '@mui/material/Typography';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import { withTranslation } from 'react-i18next';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { authService } from 'src/app/services';
|
||||
import * as yup from 'yup';
|
||||
import LeftSideCanvas from '../shared-components/LeftSideCanvas';
|
||||
|
||||
@@ -19,7 +20,7 @@ function ForgotPasswordPage({ t }) {
|
||||
email: yup.string().email(t('email_error')).required(t('email_error')),
|
||||
});
|
||||
|
||||
const { control, formState, handleSubmit, reset } = useForm({
|
||||
const { control, formState, handleSubmit, setError } = useForm({
|
||||
mode: 'onChange',
|
||||
defaultValues,
|
||||
resolver: yupResolver(schema),
|
||||
@@ -27,8 +28,13 @@ function ForgotPasswordPage({ t }) {
|
||||
|
||||
const { isValid, dirtyFields, errors } = formState;
|
||||
|
||||
function onSubmit() {
|
||||
reset(defaultValues);
|
||||
function onSubmit({ email }) {
|
||||
authService.sendPasswordResetEmail(email).catch((error) => {
|
||||
setError('root', {
|
||||
type: 'manual',
|
||||
message: error.message,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import i18next from 'i18next';
|
||||
|
||||
import SignInPage from './SignInPage';
|
||||
import authRoles from '../../../auth/authRoles';
|
||||
import authRoles from '../../../configs/authRoles';
|
||||
import en from './i18n/en';
|
||||
|
||||
i18next.addResourceBundle('en', 'signInPage', en);
|
||||
|
||||
@@ -10,13 +10,14 @@ import Typography from '@mui/material/Typography';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import { withTranslation } from 'react-i18next';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { authService } from 'src/app/services';
|
||||
import * as yup from 'yup';
|
||||
import LeftSideCanvas from '../shared-components/LeftSideCanvas';
|
||||
|
||||
const defaultValues = {
|
||||
email: '',
|
||||
password: '',
|
||||
remember: true,
|
||||
remember: false,
|
||||
};
|
||||
|
||||
function SignInPage({ t }) {
|
||||
@@ -25,7 +26,7 @@ function SignInPage({ t }) {
|
||||
password: yup.string().required(t('password_error')).min(8, t('password_error')),
|
||||
});
|
||||
|
||||
const { control, formState, handleSubmit, reset } = useForm({
|
||||
const { control, formState, handleSubmit, setError } = useForm({
|
||||
mode: 'onChange',
|
||||
defaultValues,
|
||||
resolver: yupResolver(schema),
|
||||
@@ -33,8 +34,13 @@ function SignInPage({ t }) {
|
||||
|
||||
const { isValid, dirtyFields, errors } = formState;
|
||||
|
||||
function onSubmit() {
|
||||
reset(defaultValues);
|
||||
function onSubmit(data) {
|
||||
authService.signInWithEmailAndPassword(data).catch((error) => {
|
||||
setError('root', {
|
||||
type: 'manual',
|
||||
message: error.message,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -145,6 +151,7 @@ function SignInPage({ t }) {
|
||||
>
|
||||
{t('sign_in_btn')}
|
||||
</Button>
|
||||
{errors.root && <p>{errors.root.message}</p>}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import Typography from '@mui/material/Typography';
|
||||
import Paper from '@mui/material/Paper';
|
||||
import { useEffect } from 'react';
|
||||
import JwtService from '../../../auth/services/jwtService';
|
||||
import { authService } from 'src/app/services';
|
||||
|
||||
function SignOutPage() {
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
JwtService.logout();
|
||||
authService.logout();
|
||||
}, 1000);
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import i18next from 'i18next';
|
||||
|
||||
import SignUpPage from './SignUpPage';
|
||||
import authRoles from '../../../auth/authRoles';
|
||||
import authRoles from '../../../configs/authRoles';
|
||||
import en from './i18n/en';
|
||||
|
||||
i18next.addResourceBundle('en', 'signUpPage', en);
|
||||
|
||||
@@ -11,6 +11,7 @@ import Typography from '@mui/material/Typography';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import { withTranslation } from 'react-i18next';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { authService } from 'src/app/services';
|
||||
import * as yup from 'yup';
|
||||
import LeftSideCanvas from '../shared-components/LeftSideCanvas';
|
||||
|
||||
@@ -31,7 +32,7 @@ function SignUpPage({ t }) {
|
||||
acceptTermsConditions: yup.boolean().oneOf([true], t('accept_terms_error')),
|
||||
});
|
||||
|
||||
const { control, formState, handleSubmit, reset } = useForm({
|
||||
const { control, formState, handleSubmit, setError } = useForm({
|
||||
mode: 'onChange',
|
||||
defaultValues,
|
||||
resolver: yupResolver(schema),
|
||||
@@ -39,8 +40,19 @@ function SignUpPage({ t }) {
|
||||
|
||||
const { isValid, dirtyFields, errors } = formState;
|
||||
|
||||
function onSubmit() {
|
||||
reset(defaultValues);
|
||||
function onSubmit({ name, password, email }) {
|
||||
authService
|
||||
.createUser({
|
||||
displayName: name,
|
||||
password,
|
||||
email,
|
||||
})
|
||||
.catch((error) => {
|
||||
setError('root', {
|
||||
type: 'manual',
|
||||
message: error.message,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -12,6 +12,7 @@ const HomeConfig = {
|
||||
style: 'layout2',
|
||||
},
|
||||
},
|
||||
auth: null,
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import i18next from 'i18next';
|
||||
|
||||
import { authRoles } from 'src/app/auth';
|
||||
import authRoles from '../../../configs/authRoles';
|
||||
import Dashboard from './Dashboard';
|
||||
import en from './i18n/en';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import i18next from 'i18next';
|
||||
|
||||
import { authRoles } from 'src/app/auth';
|
||||
import authRoles from '../../../configs/authRoles';
|
||||
import Favorites from './Favorites';
|
||||
import en from './i18n/en';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import i18next from 'i18next';
|
||||
|
||||
import { authRoles } from 'src/app/auth';
|
||||
import authRoles from '../../../configs/authRoles';
|
||||
import History from './History';
|
||||
import en from './i18n/en';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import i18next from 'i18next';
|
||||
|
||||
import { authRoles } from 'src/app/auth';
|
||||
import authRoles from '../../../configs/authRoles';
|
||||
import en from './i18n/en';
|
||||
import Profile from './Profile';
|
||||
|
||||
|
||||
137
src/app/services/authService.js
Normal file
137
src/app/services/authService.js
Normal file
@@ -0,0 +1,137 @@
|
||||
import FuseUtils from '@fuse/utils/FuseUtils';
|
||||
import _ from '@lodash';
|
||||
import * as firebaseAuth from 'firebase/auth';
|
||||
import * as firebaseDb from 'firebase/database';
|
||||
|
||||
export default class AuthService extends FuseUtils.EventEmitter {
|
||||
#auth;
|
||||
|
||||
#db;
|
||||
|
||||
init(authInstance, dbInstance) {
|
||||
this.#auth = authInstance;
|
||||
this.#db = dbInstance;
|
||||
}
|
||||
|
||||
createUser = ({ displayName, email, password }) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
firebaseAuth
|
||||
.createUserWithEmailAndPassword(this.#auth, email, password)
|
||||
.then((userCredential) => firebaseAuth.updateProfile(userCredential.user, { displayName }))
|
||||
.then(() => {
|
||||
const userRef = firebaseDb.ref(this.#db, `users/${this.#auth.currentUser.uid}`);
|
||||
const value = { role: 'user', data: { displayName, email } };
|
||||
|
||||
return firebaseDb.set(userRef, value);
|
||||
})
|
||||
.then(() => {
|
||||
resolve();
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
signInWithEmailAndPassword = ({ email, password, remember }) => {
|
||||
const persistence = remember
|
||||
? firebaseAuth.browserLocalPersistence
|
||||
: firebaseAuth.browserSessionPersistence;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
firebaseAuth
|
||||
.setPersistence(this.#auth, persistence)
|
||||
.then(() => firebaseAuth.signInWithEmailAndPassword(this.#auth, email, password))
|
||||
.then((userCredential) => {
|
||||
resolve(userCredential.user);
|
||||
const { user } = userCredential;
|
||||
this.emit('onLogin', {
|
||||
role: 'user',
|
||||
data: { displayName: user.displayName, photoURL: user.photoURL, email: user.email },
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
updateUserData = (user) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (_.isEmpty(user)) {
|
||||
reject(Error('User data is empty'));
|
||||
}
|
||||
|
||||
const userRef = firebaseDb.ref(this.#db, `users/${this.#auth.currentUser.uid}`);
|
||||
const value = { data: { ...user } };
|
||||
|
||||
firebaseDb
|
||||
.set(userRef, value)
|
||||
.then(() => {
|
||||
if (user.email) {
|
||||
return firebaseAuth.updateEmail(this.#auth, user.email);
|
||||
}
|
||||
|
||||
return null;
|
||||
})
|
||||
.then(() => {
|
||||
resolve();
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
logout = () => {
|
||||
this.#auth
|
||||
.signOut()
|
||||
.then(() => {
|
||||
this.emit('onLogout', 'Logged out');
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn('Logout error: ', error);
|
||||
});
|
||||
};
|
||||
|
||||
getUserData = (userId) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const userRef = firebaseDb.ref(this.#db, `users/${userId}`);
|
||||
firebaseDb.onValue(
|
||||
userRef,
|
||||
(snapshot) => {
|
||||
const user = snapshot.val();
|
||||
resolve(user);
|
||||
},
|
||||
(error) => {
|
||||
reject(error);
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
sendPasswordResetEmail = (email) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (email) {
|
||||
reject(Error('Email is empty'));
|
||||
}
|
||||
|
||||
firebaseAuth
|
||||
.sendPasswordResetEmail(this.#auth, email)
|
||||
.then(() => {
|
||||
resolve();
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
onAuthStateChanged = (callback) => {
|
||||
if (!this.#auth) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#auth.onAuthStateChanged(callback);
|
||||
};
|
||||
}
|
||||
15
src/app/services/firebaseService.js
Normal file
15
src/app/services/firebaseService.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import { initializeApp } from 'firebase/app';
|
||||
import { getAuth } from 'firebase/auth';
|
||||
import { getDatabase } from 'firebase/database';
|
||||
import { getStorage } from 'firebase/storage';
|
||||
|
||||
export default class FirebaseService {
|
||||
#app;
|
||||
|
||||
constructor(config) {
|
||||
this.app = initializeApp(config);
|
||||
this.auth = getAuth(this.app);
|
||||
this.db = getDatabase(this.app);
|
||||
this.storage = getStorage(this.app);
|
||||
}
|
||||
}
|
||||
18
src/app/services/index.js
Normal file
18
src/app/services/index.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import AuthService from './authService';
|
||||
import FirebaseService from './firebaseService';
|
||||
// TODO: change to firebase secrets or to use firebase functions
|
||||
const firebaseConfig = {
|
||||
apiKey: 'AIzaSyBqMGmOF0-DkYDpnsmZwpf5S8w5cL3fBb8',
|
||||
authDomain: 'rental-calculator-13a9e.firebaseapp.com',
|
||||
databaseURL: 'https://rental-calculator-13a9e-default-rtdb.firebaseio.com',
|
||||
projectId: 'rental-calculator-13a9e',
|
||||
storageBucket: 'rental-calculator-13a9e.appspot.com',
|
||||
messagingSenderId: '479612883365',
|
||||
appId: '1:479612883365:web:fde2d2632ce4c42ce5184c',
|
||||
measurementId: 'G-JW7J8ZQ9FJ',
|
||||
};
|
||||
|
||||
const firebase = new FirebaseService(firebaseConfig);
|
||||
const authService = new AuthService();
|
||||
|
||||
export { authService, firebase };
|
||||
@@ -5,7 +5,7 @@ import _ from '@lodash';
|
||||
import { setInitialSettings } from 'app/store/fuse/settingsSlice';
|
||||
import { showMessage } from 'app/store/fuse/messageSlice';
|
||||
import settingsConfig from 'app/configs/settingsConfig';
|
||||
import jwtService from '../auth/services/jwtService';
|
||||
import { authService } from '../services';
|
||||
|
||||
export const setUser = createAsyncThunk('user/setUser', async (user, { dispatch, getState }) => {
|
||||
/*
|
||||
@@ -30,24 +30,6 @@ export const updateUserSettings = createAsyncThunk(
|
||||
}
|
||||
);
|
||||
|
||||
export const updateUserShortcuts = createAsyncThunk(
|
||||
'user/updateShortucts',
|
||||
async (shortcuts, { dispatch, getState }) => {
|
||||
const { user } = getState();
|
||||
const newUser = {
|
||||
...user,
|
||||
data: {
|
||||
...user.data,
|
||||
shortcuts,
|
||||
},
|
||||
};
|
||||
|
||||
dispatch(updateUserData(newUser));
|
||||
|
||||
return newUser;
|
||||
}
|
||||
);
|
||||
|
||||
export const logoutUser = () => async (dispatch, getState) => {
|
||||
const { user } = getState();
|
||||
|
||||
@@ -71,10 +53,10 @@ export const updateUserData = (user) => async (dispatch, getState) => {
|
||||
return;
|
||||
}
|
||||
|
||||
jwtService
|
||||
authService
|
||||
.updateUserData(user)
|
||||
.then(() => {
|
||||
dispatch(showMessage({ message: 'User data saved with api' }));
|
||||
dispatch(showMessage({ message: 'User data saved' }));
|
||||
})
|
||||
.catch((error) => {
|
||||
dispatch(showMessage({ message: error.message }));
|
||||
@@ -87,7 +69,6 @@ const initialState = {
|
||||
displayName: 'John Doe',
|
||||
photoURL: 'assets/images/avatars/brian-hughes.jpg',
|
||||
email: 'johndoe@withinpixels.com',
|
||||
shortcuts: ['apps.calendar', 'apps.mailbox', 'apps.contacts', 'apps.tasks'],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -99,7 +80,6 @@ const userSlice = createSlice({
|
||||
},
|
||||
extraReducers: {
|
||||
[updateUserSettings.fulfilled]: (state, action) => action.payload,
|
||||
[updateUserShortcuts.fulfilled]: (state, action) => action.payload,
|
||||
[setUser.fulfilled]: (state, action) => action.payload,
|
||||
},
|
||||
});
|
||||
@@ -108,6 +88,4 @@ export const { userLoggedOut } = userSlice.actions;
|
||||
|
||||
export const selectUser = ({ user }) => user;
|
||||
|
||||
export const selectUserShortcuts = ({ user }) => user.data.shortcuts;
|
||||
|
||||
export default userSlice.reducer;
|
||||
|
||||
@@ -2,7 +2,7 @@ import FuseDialog from '@fuse/core/FuseDialog';
|
||||
import FuseMessage from '@fuse/core/FuseMessage';
|
||||
import FuseSuspense from '@fuse/core/FuseSuspense';
|
||||
import { styled } from '@mui/material/styles';
|
||||
import AppContext from 'app/AppContext';
|
||||
import AppContext from 'src/app/contexts/AppContext';
|
||||
import { selectFuseCurrentLayoutConfig } from 'app/store/fuse/settingsSlice';
|
||||
import { memo, useContext } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import FuseSuspense from '@fuse/core/FuseSuspense';
|
||||
import AppContext from 'app/AppContext';
|
||||
import AppContext from 'src/app/contexts/AppContext';
|
||||
import { selectFuseCurrentLayoutConfig } from 'app/store/fuse/settingsSlice';
|
||||
import i18next from 'i18next';
|
||||
import { memo, useContext } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useRoutes } from 'react-router-dom';
|
||||
import { useAuth } from 'src/app/contexts/AuthContext';
|
||||
import FooterLayout2 from './components/FooterLayout2';
|
||||
import HeaderLayout2 from './components/HeaderLayout2';
|
||||
|
||||
@@ -14,12 +15,13 @@ i18next.addResourceBundle('en', 'layout2', en);
|
||||
|
||||
function Layout2(props) {
|
||||
const config = useSelector(selectFuseCurrentLayoutConfig);
|
||||
const authContext = useAuth();
|
||||
const appContext = useContext(AppContext);
|
||||
const { routes } = appContext;
|
||||
|
||||
return (
|
||||
<>
|
||||
{config.header.display && <HeaderLayout2 />}
|
||||
{config.header.display && <HeaderLayout2 isAuthenticated={authContext.isAuthenticated} />}
|
||||
<main id="fuse-main" className="flex flex-col min-h-full min-w-full mt-72">
|
||||
<FuseSuspense>{useRoutes(routes)}</FuseSuspense>
|
||||
{props.children}
|
||||
|
||||
@@ -7,8 +7,8 @@ function FooterLayout2() {
|
||||
const { t } = useTranslation('layout2');
|
||||
|
||||
return (
|
||||
<footer className="w-full bg-gray-900">
|
||||
<div className="flex gap-96 max-w-screen-2xl px-10 py-52">
|
||||
<footer className="flex items-center justify-center w-full bg-gray-900">
|
||||
<div className="flex gap-96 w-full max-w-screen-xl px-10 py-52">
|
||||
<ul className="flex flex-col gap-16 mr-96">
|
||||
<li>
|
||||
<Link to="/">
|
||||
|
||||
@@ -4,12 +4,12 @@ import { useTranslation } from 'react-i18next';
|
||||
import { Link } from 'react-router-dom';
|
||||
import linksConfigLayout2 from './linksLayout2Config';
|
||||
|
||||
function HeaderLayout2() {
|
||||
function HeaderLayout2(props) {
|
||||
const { t } = useTranslation('layout2');
|
||||
|
||||
return (
|
||||
<header className="fixed z-50 flex items-center justify-center w-full h-72 px-10 bg-white">
|
||||
<div className="flex justify-between max-w-screen-2xl w-full">
|
||||
<div className="flex justify-between max-w-screen-xl w-full">
|
||||
<Link to="/">
|
||||
<img
|
||||
className="max-w-[88px] max-h-[45px]"
|
||||
@@ -17,7 +17,7 @@ function HeaderLayout2() {
|
||||
alt={t('logo_alt')}
|
||||
/>
|
||||
</Link>
|
||||
<nav className="flex gap-72 items-center">
|
||||
<nav className="flex grow justify-center gap-72 items-center">
|
||||
{linksConfigLayout2.map((path) => (
|
||||
<Link
|
||||
className="text-lg leading-5 text-slate-800 no-underline"
|
||||
@@ -28,18 +28,20 @@ function HeaderLayout2() {
|
||||
</Link>
|
||||
))}
|
||||
</nav>
|
||||
<div className="flex gap-32 items-center">
|
||||
<Link className="text-indigo-400" to="/sign-up">
|
||||
{t('sign_up')}
|
||||
</Link>
|
||||
<Link
|
||||
className="flex gap-7 items-center px-24 py-10 text-lg leading-5 text-white bg-indigo-400 rounded-2xl"
|
||||
to="/sign-in"
|
||||
>
|
||||
<span>{t('sign_in')}</span>
|
||||
<FuseSvgIcon>heroicons-outline:login</FuseSvgIcon>
|
||||
</Link>
|
||||
</div>
|
||||
{props.isAuthenticated || (
|
||||
<div className="flex gap-32 items-center">
|
||||
<Link className="text-indigo-400" to="/sign-in">
|
||||
{t('sign_in')}
|
||||
</Link>
|
||||
<Link
|
||||
className="flex gap-7 items-center px-24 py-10 text-lg leading-5 text-white bg-indigo-400 rounded-2xl"
|
||||
to="/sign-up"
|
||||
>
|
||||
<span>{t('sign_up')}</span>
|
||||
<FuseSvgIcon>heroicons-outline:login</FuseSvgIcon>
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
|
||||
@@ -3,8 +3,8 @@ const locale = {
|
||||
'about-us': 'About Us',
|
||||
blog: 'Blog',
|
||||
contacts: 'Contacts',
|
||||
sign_up: 'Log In',
|
||||
sign_in: 'Register',
|
||||
sign_in: 'Log In',
|
||||
sign_up: 'Register',
|
||||
logo_alt: 'Logo',
|
||||
};
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ function UserNavbarHeader(props) {
|
||||
src={user.data.photoURL}
|
||||
alt={user.data.displayName}
|
||||
>
|
||||
{user.data.displayName.charAt(0)}
|
||||
{user.data.displayName?.charAt(0)}
|
||||
</Avatar>
|
||||
</div>
|
||||
<Typography className="username text-14 whitespace-nowrap font-medium">
|
||||
|
||||
@@ -9,7 +9,7 @@ import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
|
||||
import { StyledEngineProvider } from '@mui/material/styles';
|
||||
import routes from 'app/configs/routesConfig';
|
||||
import store from './store';
|
||||
import AppContext from './AppContext';
|
||||
import AppContext from './contexts/AppContext';
|
||||
|
||||
const withAppProviders = (Component) => (props) => {
|
||||
const WrapperComponent = () => (
|
||||
|
||||
@@ -1192,7 +1192,8 @@ module.exports = {
|
||||
sm: '600px',
|
||||
md: '960px',
|
||||
lg: '1280px',
|
||||
xl: '1920px',
|
||||
xl: '1420px',
|
||||
'2xl': '1920px',
|
||||
print: { raw: 'print' },
|
||||
},
|
||||
scrollMargin: ({ theme }) => ({
|
||||
|
||||
Reference in New Issue
Block a user