In my company I am developing a React application starting from the project base used by the company.
In the app we manage the user login by calling an API that responds with an access_token, and it work well.
The access_token must therefore be saved on the browser's local storage.
I then configured the API and I make the authentication API request, and it answers me correctly with the authentication token, but it is not saved on the browser's local storage.
Do you have any suggestions or advice? Did I do something wrong or do I need to change the basic template of the project?
MyCode:
auth.js
import api from "../api/axios";
import jwt_decode from "jwt-decode";
// Set all future requests to use the token.
const setToken = () => {
const token = localStorage.getItem("token");
if (token) {
api.interceptors.request.use(
async (config) => {
config.headers = {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
useCredentials: true,
};
return config;
},
(error) => {
Promise.reject(error);
}
);
}
};
// Get new Token by API CALL.
const tokenAPICALL = async (user, pass) => {
if (user && pass) {
const { data } = await api.post(
`/login`,
{
email: user,
password: pass,
},
{
method: 'POST',
headers: {
"Content-Type": "application/json; charset=UTF-8",
},
}
).then((response) => {
console.log(response);
}, (error) => {
console.log(error);
});
const jwt_Token_decoded = jwt_decode(data.data.access_token);
const expiryDate = jwt_Token_decoded.exp;
if (jwt_Token_decoded.exp * 1000 > Date.now()) {
localStorage.setItem("token", data.data.access_token);
localStorage.setItem("token_exp", expiryDate);
} else {
console.log("failed token");
}
return data.data.access_token;
}
return null;
};
// Check the Token stored in Cookies.
const getToken = async (user, pass) => {
const storedJwt = localStorage.getItem("token");
const expiration = localStorage.getItem("token_exp");
if (expiration && storedJwt) {
if (expiration * 1000 > Date.now()) {
console.log("Keep Token");
setToken();
} else {
console.log("Expired Token");
return await tokenAPICALL(user, pass);
}
} else {
// No token recorder in session, Get new token
console.log("No Token");
return await tokenAPICALL(user, pass);
}
};
export default getToken;
EDIT: (More code to clarify)
LoginForm.js
import React, { useState } from "react";
import Avatar from '#material-ui/core/Avatar';
import Button from '#material-ui/core/Button';
import CssBaseline from '#material-ui/core/CssBaseline';
import TextField from '#material-ui/core/TextField';
import FormControlLabel from '#material-ui/core/FormControlLabel';
import Checkbox from '#material-ui/core/Checkbox';
import Link from '#material-ui/core/Link';
import Grid from '#material-ui/core/Grid';
import Box from '#material-ui/core/Box';
import LockOutlinedIcon from '#material-ui/icons/LockOutlined';
import Typography from '#material-ui/core/Typography';
import { makeStyles } from '#material-ui/core/styles';
import Container from '#material-ui/core/Container';
import history from './../../history';
import getToken from "../../auth/auth";
function Copyright() {
return (
<Typography variant="body2" color="textSecondary" align="center">
{'Copyright © '}
<Link color="inherit" href="https://material-ui.com/">
Your Website
</Link>{' '}
{new Date().getFullYear()}
{'.'}
</Typography>
);
}
const useStyles = makeStyles((theme) => ({
paper: {
marginTop: theme.spacing(8),
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
},
avatar: {
margin: theme.spacing(1),
backgroundColor: theme.palette.secondary.main,
},
form: {
width: '100%', // Fix IE 11 issue.
marginTop: theme.spacing(1),
},
submit: {
margin: theme.spacing(3, 0, 2),
},
}));
export default function LoginPage() {
const classes = useStyles();
const [alertState, setAlertstate] = useState({
open: false,
vertical: "bottom",
horizontal: "center",
severity: "success",
message: "",
});
const { vertical, horizontal, open, severity, message } = alertState;
const [loginObj, setLoginObj] = useState({ username: "", password: "" });
const handleSubmit = async (e) => {
try {
localStorage.clear();
var jwtres = await getToken(loginObj.username, loginObj.password);
if (jwtres) {
setAlertstate({
open: true,
vertical: "bottom",
horizontal: "center",
message: "Login Successfully.",
severity: "success",
});
history.push("/dashboard");
} else {
setAlertstate({
open: true,
vertical: "bottom",
horizontal: "center",
message: "Login failed. Please try again.",
severity: "error",
});
}
} catch (e) {
setAlertstate({
open: true,
vertical: "bottom",
horizontal: "center",
message: "Invalid Data. Please try again.",
severity: "error",
});
}
};
return (
<Container component="main" maxWidth="xs">
<CssBaseline />
<div className={classes.paper}>
<Avatar className={classes.avatar}>
<LockOutlinedIcon />
</Avatar>
<Typography component="h1" variant="h5">
Sign in
</Typography>
<form className={classes.form} noValidate>
<TextField
variant="outlined"
margin="normal"
required
fullWidth
id="email"
label="Email Address"
name="email"
autoComplete="email"
onChange={(e) => {
setLoginObj({ ...loginObj, username: e.target.value });
}}
autoFocus
/>
<TextField
variant="outlined"
margin="normal"
required
fullWidth
name="password"
label="Password"
type="password"
id="password"
onChange={(e) => {
setLoginObj({ ...loginObj, password: e.target.value });
}}
autoComplete="current-password"
/>
<FormControlLabel
control={<Checkbox value="remember" color="primary" />}
label="Remember me"
/>
<Button
type="button"
fullWidth
variant="contained"
color="primary"
onClick={handleSubmit}
className={classes.submit}
>
Sign In
</Button>
<Grid container>
<Grid item xs>
<Link href="#" variant="body2">
Forgot password?
</Link>
</Grid>
<Grid item>
<Link href="/signup" variant="body2">
{"Don't have an account? Sign Up"}
</Link>
</Grid>
</Grid>
</form>
</div>
<Box mt={8}>
<Copyright />
</Box>
</Container>
);
}
axios.js
import axios from 'axios';
import history from "../history";
const apiUrl = 'https://mysite/api/auth'; //your api base url
const proxyurl = "https://myproxy.io/fetch/"; //proxy for local testing, remove this in production version
function getHeader() {
const token = localStorage.getItem("token");
const expiration = localStorage.getItem("token_exp");
if (expiration * 1000 < Date.now()) {
history.push("/");
return {
"Content-Type": "application/json",
};
}
if (token) {
return {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
useCredentials: true,
};
} else {
return {
"Content-Type": "application/json",
};
}
}
const api = axios.create({
baseURL: apiUrl,
headers: getHeader(),
})
export default api;
You are mixing up async/await with promise handlers, use any one of them or remove the promise handlers (then, catch) from post call and it should work,
In current situation you won't be receiving data object, that's why you are facing this issue.
it is because in the tokenAPICALL you are consuming your api call promise with both await and .then() in that case your response will be logged to the console but then destructuring (const {data} = ...) won't work.
try this:
const {data} = await api.post(
`/login`,
{
email: user,
password: pass,
},
{
method: 'POST',
headers: {
"Content-Type": "application/json; charset=UTF-8",
},
}
)
Related
I'm using Django and React to make a YouTube clone.
I tried to post the video on the frontend side, and got an error in terminal:
<QueryDict: {'title': ['Test Video Creation'], 'user': ['1'], 'description': ['Is it working?'], 'image': [<TemporaryUploadedFile: attachment.png (image/png)>], 'video': [<TemporaryUploadedFile: video-for-fatube.mp4 (video/mp4)>]}>Bad Request: /api/admin/create/
And the problem is not in the backend settings, because when I made a post request in the postman, the video was succesfully created.
creat.js File on frontend side to create videos.
import React, { useState } from 'react';
import axiosInstance from '../../axios';
import { useHistory } from 'react-router-dom';
//MaterialUI
import Avatar from '#material-ui/core/Avatar';
import axios from "axios";
import Button from '#material-ui/core/Button';
import CssBaseline from '#material-ui/core/CssBaseline';
import TextField from '#material-ui/core/TextField';
import Grid from '#material-ui/core/Grid';
import Typography from '#material-ui/core/Typography';
import { makeStyles } from '#material-ui/core/styles';
import Container from '#material-ui/core/Container';
import IconButton from '#material-ui/core/IconButton';
import PhotoCamera from '#material-ui/icons/PhotoCamera';
const useStyles = makeStyles((theme) => ({
paper: {
marginTop: theme.spacing(8),
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
},
avatar: {
margin: theme.spacing(1),
backgroundColor: theme.palette.secondary.main,
},
form: {
width: '100%', // Fix IE 11 issue.
marginTop: theme.spacing(3),
},
submit: {
margin: theme.spacing(3, 0, 2),
},
}));
export default function Create() {
const history = useHistory();
const initialFormData = Object.freeze({
title: '',
description: '',
});
const [videoData, updateFormData] = useState(initialFormData);
const [videoimage, setVideoImage] = useState(null);
const [videovideo, setVideoVideo] = useState(null);
const handleChange = (e) => {
if ([e.target.name] == 'image') {
setVideoImage({
image: e.target.files,
});
console.log(e.target.files);
}
if ([e.target.name] == 'video') {
setVideoVideo({
video: e.target.files,
});
console.log(e.target.files);
}
if ([e.target.name] == 'title') {
updateFormData({
...videoData,
[e.target.name]: e.target.value.trim(),
});
} else {
updateFormData({
...videoData,
[e.target.name]: e.target.value.trim(),
});
}
};
const handleSubmit = (e) => {
const config = { headers: { 'Content-Type': 'multipart/form-data' } };
const URL = 'http://127.0.0.1:8000/api/admin/create/';
let formData = new FormData();
formData.append('title', videoData.title);
formData.append('user', 1);
formData.append('description', videoData.description);
formData.append('image', videoimage.image[0]);
formData.append('video', videovideo.video[0]);
axios
.post(URL, formData, config)
.then((res) => {
console.log(res.data);
})
.catch((err) => console.log(err));
};
const classes = useStyles();
return (
<Container component="main" maxWidth="xs">
<CssBaseline />
<div className={classes.paper}>
<Avatar className={classes.avatar}></Avatar>
<Typography component="h1" variant="h5">
Create New Video
</Typography>
<form className={classes.form} noValidate>
<Grid container spacing={2}>
<Grid item xs={12}>
<TextField
variant="outlined"
required
fullWidth
id="title"
label="Video Title"
name="title"
autoComplete="title"
onChange={handleChange}
/>
</Grid>
<Grid item xs={12}>
<TextField
variant="outlined"
required
fullWidth
id="description"
label="Video Description"
name="description"
autoComplete="description"
onChange={handleChange}
multiline
rows={4}
/>
</Grid>
<input
accept="image/*"
className={classes.input}
id="video-image"
onChange={handleChange}
name="image"
type="file"
/>
<input
accept="video/*"
className={classes.input}
id="video-video"
onChange={handleChange}
name="video"
type="file"
/>
</Grid>
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
className={classes.submit}
onClick={handleSubmit}
>
Create Video
</Button>
</form>
</div>
</Container>
);
}
If you want to look at the whole project, here is Github: https://github.com/PHILLyaHI/diplom-work
What I did to make it work is I deleted files in the media folder and deleted all the videos.
I am new to stripe and APIs and followed a tutorial to implement stripe checkout and implemented that successfully but I want to display a receipt after stripe checkout.
Do you know any such tutorial or can anyone please help.
Also, after the payment, in the stripe dashboard I am getting status as incomplete and it says that three_d_secure authentication is required which I had turned on in the setting but whenever I enter card details it never asks. Would like to learn that too. Let me know if there are any good resources for the same. Would be grateful to have someone guideme.
Code for listening on port 4000
const express = require("express")
const app = express()
require("dotenv").config()
const stripe = require("stripe")(process.env.STRIPE_SECRET_TEST)
const bodyParser = require("body-parser")
const cors = require("cors")
app.use(bodyParser.urlencoded({ extended: true }))
app.use(bodyParser.json())
app.use(cors())
app.post("/payment", cors(), async (req, res) => {
let { amount, id } = req.body
try {
const payment = await stripe.paymentIntents.create({
amount,
currency: "USD",
description: "pay",
payment_method: id,
confirm: true
})
console.log("Payment", payment)
res.json({
message: "Payment successful",
success: true
})
} catch (error) {
console.log("Error", error)
res.json({
message: "Payment failed",
success: false
})
}
})
app.listen(process.env.PORT || 4000, () => {
console.log("Sever is listening on port 4000")
})
code for checkout form
import { CardElement, useElements, useStripe } from "#stripe/react-stripe-js"
import { CardNumberElement, CardExpiryElement, CardCvcElement } from "#stripe/react-stripe-js";
import {ArrowForward } from '#material-ui/icons';
import axios from "axios"
import React, { useState } from 'react'
import './styles.css';
import { DataGrid } from "#material-ui/data-grid";
import { Typography } from "#material-ui/core";
import { Alert } from "#material-ui/core";
import { LoadingButton } from "#material-ui/lab";
import {Button, TextField, Paper, Grid } from "#material-ui/core";
import StripeInput from "../../src/components/StripeInput";
const CARD_OPTIONS = {
iconStyle: "solid",
style: {
base: {
iconColor: "#c4f0ff",
color: "#fff",
fontWeight: 500,
fontFamily: "Roboto, Open Sans, Segoe UI, sans-serif",
fontSize: "16px",
fontSmoothing: "antialiased",
":-webkit-autofill": { color: "#fce883" },
"::placeholder": { color: "#87bbfd" }
},
invalid: {
iconColor: "#ffc7ee",
color: "#ffc7ee"
}
}
}
export default function PaymentForm() {
const [success, setSuccess ] = useState(false)
const stripe = useStripe()
const elements = useElements()
const [loading, setLoading] = React.useState(false);
const handleSubmit = async (e) => {
setLoading(true);
e.preventDefault()
const {error, paymentMethod} = await stripe.createPaymentMethod({
type: "card",
card: elements.getElement(CardNumberElement)
})
if(!error) {
try {
const {id} = paymentMethod
const response = await axios.post("http://localhost:4000/payment", {
amount: 1000,
id
})
if(response.data.success) {
console.log("Successful payment")
setSuccess(true)
}
} catch (error) {
console.log("Error", error)
}
} else {
console.log(error.message)
}
}
const paperStyle={padding:'30px 20px', width: 600, margin: "20px auto"}
const marginTop = { marginTop: 10}
return (
<center>
<>
{!success ?
<div>
<Grid>
<Paper elevation ={20} style = {paperStyle} >
<Grid align='center'></Grid>
<form>
<Grid item xs ={12}></Grid>
<Grid item xs={12} sm={6}>
<TextField
//placeholder="XXXX XXXX XXXX XXXX"
label="Card Number"
style={marginTop}
fullWidth
InputLabelProps={{
shrink: true,
}}
InputProps={{
inputComponent: StripeInput,
inputProps: {
component: CardNumberElement
},
}}
/>
</Grid>
<Grid item xs={12} sm={6}>
<TextField
//placeholder="MM/YY"
fullWidth
label="Expiry Date"
style={marginTop}
InputLabelProps={{
shrink: true,
}}
InputProps={{
inputComponent: StripeInput,
inputProps: {
component: CardExpiryElement
},
}}
/>
</Grid>
<Grid item xs ={12}></Grid>
<Grid item xs={12} sm={6}>
<TextField
//placeholder="XXX"
fullWidth
label="CVV"
style={marginTop}
InputLabelProps={{
shrink: true,
}}
InputProps={{
inputComponent: StripeInput,
inputProps: {
component: CardCvcElement
},
}}
/>
</Grid>
<Grid item xs ={12}></Grid>
<Grid item xs={12} sm={6}>
<LoadingButton
loading ={loading}
loadingPosition="start"
startIcon={<ArrowForward />}
fullWidth
variant="outlined"
onClick={handleSubmit}
style={marginTop}>
Pay
</LoadingButton>
</Grid>
</form>
</Paper>
</Grid>
<center>
</center>
</div>
:
<Grid>
<Paper elevation ={20} style = {paperStyle} >
<Grid align='center'></Grid>
<Alert severity="success">
Your payment is Successful. Thanks for using our service.
</Alert>
</Paper>
</Grid>
}
</>
</center>
)
}
You can learn about sending receipts from Stripe on successful payment or refunds here
To your second question, use card no 4000 0025 0000 3155 to test 3D secure integration. Read more here
I learn Reactjs and have this Sign in Component that uses Formik
My problem is that Facebook, Google, and Twitter work as expected but I can't get password Sign-in to work.
When I click the Button the onSubmit={(values, { setSubmitting }) => {... is called and the values contain the email and password but I want the Button to use the same logic as Facebook, Google and Twitter Button's`, if that's possible.
You see when clicking Facebook, Google, and Twitter they all onClick={this.submitFormType... that sets the state to the respective provider. Then it calls the Formik handleSubmit.
This is what I want for the submit Button also or maybe I should rethink my design not using Formik.
import React from 'react';
import { connect } from 'react-redux';
import { compose } from 'recompose';
import { Link as RouterLink } from 'react-router-dom';
import * as Yup from 'yup';
import { Formik } from 'formik';
import { Box, Button, Container, Grid, Link, TextField, Typography } from '#material-ui/core';
import { withStyles } from '#material-ui/core/styles';
import Dots from 'react-activity/lib/Dots';
import { withFirebase } from '../../../../firebase';
import FacebookIcon from '../../../../assets/Facebook';
import GoogleIcon from '../../../../assets/Google';
import TwitterIcon from '../../../../assets/twitter-logo';
import Page from '../../utils/Page';
import MotionDiv from '../../utils/MotionDiv';
import { withEmailVerification, withAuthorization, AuthUserContext } from '../../../../session';
import { changeToUserRole } from '../../../../redux/userData/user.actions';
import * as ROLES from '../../../../constants/roles';
import 'react-activity/lib/Dots/Dots.css';
import * as SIGN_IN from '../../../../constants/signinmethods';
const useStyles = theme => ({
root: {
backgroundColor: theme.palette.primary.light,
minHeight: '100vh',
paddingBottom: theme.spacing(3),
paddingTop: theme.spacing(3),
},
container: {
backgroundColor: theme.palette.primary.main,
paddingTop: theme.spacing(3),
},
facebook: {
backgroundColor: '#3b5999',
color: 'white',
'&:hover': {
backgroundColor: '#4d70ba',
},
},
google: {
backgroundColor: '#ffffff',
textColor: 'black',
},
twitter: {
backgroundColor: '#ffffff',
textColor: 'black',
},
button: {
backgroundColor: theme.palette.primary.main,
boxShadow: theme.shadows[5],
},
});
const INITIAL_EMAIL_STATE = {
email: '',
password: '',
error: null,
};
class LoginManagementBase extends React.Component {
constructor() {
super();
this.state = {
activeSignInMethods: [],
anonymousSignIn: null,
error: null,
formType: null,
...INITIAL_EMAIL_STATE,
};
this.submitFormType = this.submitFormType.bind(this);
this.facebookSignIn = this.facebookSignIn.bind(this);
this.googleSignIn = this.googleSignIn.bind(this);
this.twitteSignIn = this.twitteSignIn.bind(this);
}
componentDidMount() {
this.fetchSignInMethods();
}
fetchSignInMethods = () => {
const { firebase, authUser } = this.props;
const email = authUser.email === null ? 'none#guest.ac' : authUser.email;
firebase.auth
.fetchSignInMethodsForEmail(email)
.then(activeSignInMethods =>
this.setState({
activeSignInMethods,
anonymousSignIn: activeSignInMethods.length === 0,
error: null,
}),
)
.catch(error => this.setState({ error }));
};
onSocialLoginLink = provider => {
// Do stuf to sign in..............
};
// this.setState({ count: this.state.count + 1 })
submitFormType = (formTypeSubmited, handleSumitType) => () => {
this.setState({
formType: formTypeSubmited,
});
handleSumitType();
};
googleSignIn = () => {
this.setState({
formType: undefined,
});
this.onSocialLoginLink(SIGN_IN.WITH_GOOGLE.provider);
};
facebookSignIn = () => {
this.setState({
formType: undefined,
});
this.onSocialLoginLink(SIGN_IN.WITH_FACEBOOK.provider);
};
twitteSignIn = () => {
this.setState({
formType: undefined,
});
this.onSocialLoginLink(SIGN_IN.WITH_TWITTER.provider);
};
emailSignIn = values => {
const { email, password } = values;
const { firebase } = this.props;
firebase.auth
.signInWithEmailAndPassword(email, password)
.then(() => {
this.setState({ ...INITIAL_EMAIL_STATE });
// this.props.history.push(ROUTES.HOME);
})
.catch(error => {
this.setState({ error });
});
};
render() {
const { classes } = this.props;
const { error } = this.state;
const { saveRolesErr, isSavingRolesStarted } = this.props;
if (error && error.message) {
console.log(error.message);
}
return (
<MotionDiv>
<Page className={classes.root} title="Sign In">
<Box display="flex" flexDirection="column" height="100%" justifyContent="center">
<Container maxWidth="sm" className={classes.container}>
<Formik
initialValues={{
email: 'demo#devias.io',
password: 'Password123',
}}
validationSchema={Yup.object().shape({
email: Yup.string().email('Must be a valid email').max(255).required('Email is required'),
password: Yup.string().max(255).required('Password is required'),
})}
onSubmit={(values, { setSubmitting }) => {
setSubmitting(false);
const { formType } = this.state;
if (formType) {
if (formType === SIGN_IN.WITH_FACEBOOK.provider) {
this.facebookSignIn();
} else if (formType === SIGN_IN.WITH_GOOGLE.provider) {
this.googleSignIn();
} else if (formType === SIGN_IN.WITH_TWITTER.provider) {
this.twitteSignIn();
} else if (formType) {// BUT HERE I CAN'T SET THE formType FOR PASSWORD
this.emailSignIn(values);
}
}
}}
>
{({ errors, handleBlur, handleChange, handleSubmit, isSubmitting, touched, values }) => (
<form onSubmit={handleSubmit}>
<Box mb={3}>
<Typography color="textPrimary" variant="h2">
Sign in
</Typography>
<Typography color="textSecondary" gutterBottom variant="body2">
Sign in on the internal platform
</Typography>
</Box>
<Grid container spacing={3}>
<Grid item xs={12} md={6}>
<Button
className={classes.facebook}
fullWidth
startIcon={<FacebookIcon />}
type="button"
onClick={this.submitFormType(SIGN_IN.WITH_FACEBOOK.provider, handleSubmit)}
size="large"
variant="contained"
>
SIGN in with Facebook
</Button>
</Grid>
<Grid item xs={12} md={6}>
<Button
className={classes.google}
fullWidth
startIcon={<GoogleIcon />}
onClick={this.submitFormType(SIGN_IN.WITH_GOOGLE.provider, handleSubmit)}
size="large"
variant="contained"
>
Sign in with Google
</Button>
</Grid>
<Grid item xs={12} md={6}>
<Button
className={classes.twitter}
fullWidth
startIcon={<TwitterIcon />}
onClick={this.submitFormType(SIGN_IN.WITH_TWITTER.provider, handleSubmit)}
size="large"
variant="contained"
>
Sign in with Twitter
</Button>
</Grid>
</Grid>
<Box mt={3} mb={1}>
<Typography align="center" color="textSecondary" variant="body1">
or login with email address
</Typography>
</Box>
<TextField
error={Boolean(touched.email && errors.email)}
fullWidth
helperText={touched.email && errors.email}
label="Email Address"
margin="normal"
name="email"
onBlur={handleBlur}
onChange={handleChange}
type="email"
value={values.email}
variant="outlined"
/>
<TextField
error={Boolean(touched.password && errors.password)}
fullWidth
helperText={touched.password && errors.password}
label="Password"
margin="normal"
name="password"
onBlur={handleBlur}
onChange={handleChange}
type="password"
value={values.password}
variant="outlined"
/>
<Box my={2}>
<Button
className={classes.button}
disabled={isSubmitting}
fullWidth
size="large"
type="submit"
variant="contained"
color="primary"
>
Sign in now
</Button>
</Box>
<Typography color="textSecondary" variant="body1">
Don't have an account?{' '}
<Link color="textSecondary" component={RouterLink} to="../register" variant="h6">
Sign up!
</Link>
</Typography>
</form>
)}
</Formik>
<div>{isSavingRolesStarted ? <Dots /> : null}</div>
<h1 style={{ margin: '8px', color: 'red', textAlign: 'center', backgroundColor: 'white' }}>
{error && error.message}
{saveRolesErr && saveRolesErr.message}
</h1>
</Container>
</Box>
</Page>
</MotionDiv>
);
}
}
const mapDispatchToProps = dispatch => ({
setUserRoleToUser: () => dispatch(changeToUserRole()),
});
const mapStateToProps = state => {
return {
isSavingRolesStarted: state.user.isSavingRolesStarted,
saveRolesErr: state.user.saveRolesErrMsg,
};
};
let LoginManagement = withStyles(useStyles)(LoginManagementBase);
const enhance = compose(withFirebase, connect(mapStateToProps, mapDispatchToProps), withEmailVerification);
LoginManagement = enhance(LoginManagement);
const LoginView = () => (
<AuthUserContext.Consumer>
{authUser => (
<div>
<LoginManagement authUser={authUser} />
</div>
)}
</AuthUserContext.Consumer>
);
const condition = authUser => authUser && authUser.roles.includes(ROLES.ANON);
export default withAuthorization(condition)(LoginView);
I could not solve this now so I did React vanilla for now.
import React from 'react';
import { connect } from 'react-redux';
import { compose } from 'recompose';
import { Link as RouterLink } from 'react-router-dom';
import { Box, Button, Container, Grid, Link, TextField, Typography } from '#material-ui/core';
import { withStyles } from '#material-ui/core/styles';
import Dots from 'react-activity/lib/Dots';
import { withFirebase } from '../../../../firebase';
import FacebookIcon from '../../../../assets/Facebook';
import GoogleIcon from '../../../../assets/Google';
import TwitterIcon from '../../../../assets/twitter-logo';
import Page from '../../utils/Page';
import MotionDiv from '../../utils/MotionDiv';
import { withEmailVerification, withAuthorization, AuthUserContext } from '../../../../session';
import { changeToUserRole } from '../../../../redux/userData/user.actions';
import * as ROLES from '../../../../constants/roles';
import 'react-activity/lib/Dots/Dots.css';
import * as SIGN_IN from '../../../../constants/signinmethods';
const useStyles = theme => ({
root: {
backgroundColor: theme.palette.primary.light,
minHeight: '100vh',
paddingBottom: theme.spacing(3),
paddingTop: theme.spacing(3),
},
container: {
backgroundColor: theme.palette.primary.main,
paddingTop: theme.spacing(3),
},
textField: {
boxShadow: theme.shadows[3],
backgroundColor: theme.palette.primary.light,
},
facebook: {
backgroundColor: '#3b5999',
color: 'white',
'&:hover': {
backgroundColor: '#4d70ba',
},
},
google: {
backgroundColor: '#ffffff',
textColor: 'black',
},
twitter: {
backgroundColor: '#ffffff',
textColor: 'black',
},
signInButton: {
backgroundColor: theme.palette.primary.main,
boxShadow: theme.shadows[5],
},
});
const INITIAL_EMAIL_STATE = {
email: '',
password: '',
error: null,
message: '',
isSubmitting: false,
};
class LoginManagementBase extends React.Component {
constructor() {
super();
this.state = {
...INITIAL_EMAIL_STATE,
};
this.facebookSignIn = this.facebookSignIn.bind(this);
this.googleSignIn = this.googleSignIn.bind(this);
this.twitteSignIn = this.twitteSignIn.bind(this);
}
onSocialLoginLink = provider => {
const { firebase, setUserRoleToUser } = this.props;
firebase.auth.currentUser
.linkWithPopup(firebase[provider])
.then(res => {
if (res.credential) {
if (res.user.email) {
firebase.doLogEvent(`linkWithPopup to Firestore for: ${res.user.email}`);
} else {
firebase.doLogEvent(`linkWithPopup to Firestore for: ${res.credential.providerId}`);
}
setUserRoleToUser();
}
})
.then(this.fetchSignInMethods)
.catch(error => {
if (error.code === 'auth/credential-already-in-use') {
const anonUser = firebase.auth.currentUser;
firebase.auth
.signInWithCredential(error.credential)
.then(res => {
if (res.user.email) {
firebase.doLogEvent(`signInWithCredential to Firestore for: ${res.user.email}`);
} else {
firebase.doLogEvent(`signInWithCredential to Firestore for: ${res.credential.providerId}`);
}
setUserRoleToUser();
// remove the anonUser implications?
// TODO: As anonymouse the User can't change the content(viewer only) so removing should not be a problem
anonUser
.delete()
.then(() => {
firebase.doLogEvent(`Deleted anonUser when signing in`);
})
.catch(err => {
firebase.doLogEvent(`Error deleted anonUser when signing in: ${err}`);
firebase.doLogEvent(`Manually remove anon account: ${anonUser.uid}`);
});
})
.catch(error => {
this.setState({ error, isSubmitting: false });
});
} else if (error.code === 'auth/email-already-in-use') {
error.message = `The email address ${error.email} is already in use by another account.`;
this.setState({ error, isSubmitting: false });
} else {
this.setState({ error, isSubmitting: false });
}
});
};
googleSignIn = () => {
this.setState({
isSubmitting: true,
});
this.onSocialLoginLink(SIGN_IN.WITH_GOOGLE.provider);
};
facebookSignIn = () => {
this.setState({
isSubmitting: true,
});
this.onSocialLoginLink(SIGN_IN.WITH_FACEBOOK.provider);
};
twitteSignIn = () => {
this.setState({
isSubmitting: true,
});
this.onSocialLoginLink(SIGN_IN.WITH_TWITTER.provider);
};
emailSignIn = () => {
const { email, password } = this.state;
const { firebase } = this.props;
firebase.auth
.signInWithEmailAndPassword(email, password)
.then(() => {
this.setState({ ...INITIAL_EMAIL_STATE });
// this.props.history.push(ROUTES.HOME);
})
.catch(error => {
this.setState({ error, isSubmitting: false });
});
};
mailHandle = e => {
const mailValue = e.target.value;
this.setState({
email: mailValue,
});
};
passwordHandle = e => {
const passwordValue = e.target.value;
this.setState({
password: passwordValue,
});
};
chekvalid = () => {
const { email, password } = this.state;
const isEmailValid = this.ValidEmail(email);
const isPasswordValid = this.ValidPass(password);
if (password.length === 0 && email.length === 0) {
this.setState({
error: 'Enter E-Mail and Password to continue!',
});
}
if (!isPasswordValid) {
this.setState({
message: 'Password should have more then 5 character',
});
}
if (email.length === 0) {
this.setState({
message: 'Enter an E-Mail to continue!',
});
}
if (password.length === 0) {
this.setState({
message: 'Enter an Password to continue!',
});
}
if (!isEmailValid) {
this.setState({
message: 'E-Mail is not valid!',
});
}
if (isEmailValid && isPasswordValid) {
this.setState({
message: '',
isSubmitting: true,
});
this.emailSignIn();
}
};
ValidEmail = email => {
const re = /^(([^<>()[\]\\.,;:\s#"]+(\.[^<>()[\]\\.,;:\s#"]+)*)|(".+"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return re.test(String(email).toLowerCase());
};
ValidPass = email => {
if (email.length > 5) {
return true;
}
return false;
};
render() {
const { classes } = this.props;
const { message, error, email, password, isSubmitting } = this.state;
const { saveRolesErr, isSavingRolesStarted } = this.props;
if (error && error.message) {
console.log(error.message);
}
return (
<MotionDiv>
<Page className={classes.root} title="Sign In">
<Box display="flex" flexDirection="column" height="100%" justifyContent="center">
<Container maxWidth="sm" className={classes.container}>
<Box mb={3}>
<Typography color="textPrimary" variant="h2">
Sign in
</Typography>
<Typography color="textSecondary" gutterBottom variant="body2">
Sign in on the internal platform
</Typography>
</Box>
<Grid container spacing={3}>
<Grid item xs={12} md={6}>
<Button
className={classes.facebook}
disabled={isSubmitting}
fullWidth
startIcon={<FacebookIcon />}
type="submit"
onClick={this.facebookSignIn}
size="large"
variant="contained"
>
SIGN in with Facebook
</Button>
</Grid>
<Grid item xs={12} md={6}>
<Button
className={classes.google}
disabled={isSubmitting}
fullWidth
type="submit"
startIcon={<GoogleIcon />}
onClick={this.googleSignIn}
size="large"
variant="contained"
>
Sign in with Google
</Button>
</Grid>
<Grid item xs={12} md={6}>
<Button
className={classes.twitter}
disabled={isSubmitting}
fullWidth
type="submit"
startIcon={<TwitterIcon />}
onClick={this.twitteSignIn}
size="large"
variant="contained"
>
Sign in with Twitter
</Button>
</Grid>
</Grid>
<Box mt={3} mb={1}>
<Typography align="center" color="textSecondary" variant="body1">
or login with email address
</Typography>
</Box>
<form noValidate autoComplete="off">
<TextField
className={classes.textField}
variant="outlined"
margin="normal"
required
fullWidth
id="email"
label="Email Address"
name="email"
autoComplete="email"
value={email}
onChange={this.mailHandle}
color="secondary"
/>
</form>
<form noValidate autoComplete="off">
<TextField
className={classes.textField}
variant="outlined"
margin="normal"
required
fullWidth
name="password"
label="Password"
type="password"
id="password"
autoComplete="current-password"
value={password}
onChange={this.passwordHandle}
color="secondary"
/>
</form>
<Box my={2}>
<Button
disabled={isSubmitting}
className={classes.signInButton}
fullWidth
size="large"
type="submit"
variant="contained"
color="primary"
onClick={this.chekvalid}
>
Sign in now
</Button>
</Box>
<Typography color="textSecondary" variant="body1">
Don't have an account?{' '}
<Link color="textSecondary" component={RouterLink} to="../register" variant="h6">
Sign up!
</Link>
</Typography>
<div>{isSavingRolesStarted ? <Dots /> : null}</div>
<h1 style={{ margin: '8px', color: 'red', textAlign: 'center', backgroundColor: 'white' }}>
{message}
{error && error.message}
{saveRolesErr && saveRolesErr.message}
</h1>
</Container>
</Box>
</Page>
</MotionDiv>
);
}
}
const mapDispatchToProps = dispatch => ({
setUserRoleToUser: () => dispatch(changeToUserRole()),
});
const mapStateToProps = state => {
return {
isSavingRolesStarted: state.user.isSavingRolesStarted,
saveRolesErr: state.user.saveRolesErrMsg,
};
};
let LoginManagement = withStyles(useStyles)(LoginManagementBase);
const enhance = compose(withFirebase, connect(mapStateToProps, mapDispatchToProps), withEmailVerification);
LoginManagement = enhance(LoginManagement);
const LoginView = () => (
<AuthUserContext.Consumer>
{authUser => (
<div>
<LoginManagement authUser={authUser} />
</div>
)}
</AuthUserContext.Consumer>
);
const condition = authUser => authUser && authUser.roles.includes(ROLES.ANON);
export default withAuthorization(condition)(LoginView);
import axios from 'axios'
import Copyright from '../components/Copyright'
// Material UI Stuff
import { makeStyles } from '#material-ui/core/styles'
import CircularProgress from '#material-ui/core/CircularProgress'
import Container from '#material-ui/core/Container'
import Typography from '#material-ui/core/Typography'
import TextField from '#material-ui/core/TextField'
import Button from '#material-ui/core/Button'
import Box from '#material-ui/core/Box'
const INITIAL_STATE = {
cohort: '',
program: '',
github: '',
website: '',
linkedIn: ''
}
const inputProps = {
step: 1,
min: 1,
max: 99
}
const Profile = () => {
const [formData, setFormData] = useState(INITIAL_STATE)
const [isloading, setIsLoading] = useState(false)
const [errors, setErrors] = useState({})
const [user, setUser] = useState(null)
const isInvalid = !formData.cohort || !formData.program || isloading
const handleInputChange = field => e => {
setFormData({ ...formData, [field]: e.target.value })
}
const fetchProfile = async () => {
const token = await localStorage.FBIdToken
await axios
.get(`/user`, {
headers: {
Authorization: `${token}`
}
})
.then(res => {
setUser(res.data)
setFormData({
...formData,
github: res.data.user.github ? res.data.user.github : '',
website: res.data.user.website ? res.data.user.website : '',
linkedIn: res.data.user.linkedIn ? res.data.user.linkedIn : '',
cohort: res.data.user.cohort,
program: res.data.user.program
})
})
.catch(err => console.log('*** WRONG ***'))
}
useEffect(() => {
fetchProfile()
}, [])
const handleSubmit = async e => {
e.preventDefault()
setIsLoading(true)
const fireToken = await localStorage.FBIdToken
await axios
.post(
(`/user`,
{
headers: {
Authorization: `${fireToken}`
}
})
)
.then(res => {
console.log(res.data)
setIsLoading(false)
})
.catch(err => {
console.log(err)
setIsLoading(false)
})
}
const useStyles = makeStyles(theme => ({
form: {
textAlign: 'center',
width: '100%', // Fix IE 11 issue.
marginTop: theme.spacing(1),
position: 'relative'
},
logo: {
width: 180,
height: 180,
display: 'block',
alignItems: 'center'
},
submit: {
margin: theme.spacing(3, 0, 2),
position: 'relative'
},
progress: {
position: 'absolute'
},
customError: {
color: 'red',
fontSize: '0.8rem',
width: '100%',
position: 'absolute'
}
}))
const classes = useStyles()
return (
<>
{user ? (
<div>
<Container component="main" maxWidth="xs">
<div className={classes.paper}>
<img
src={user.user.imageUrl}
alt="wyncode logo"
className={classes.logo}
/>
<Typography variant="h3" className={classes.pageTitle}>
Sign Up
</Typography>
<form noValidate onSubmit={handleSubmit} className={classes.form}>
<TextField
variant="outlined"
margin="normal"
fullWidth
id="website"
type="website"
label="Website"
name="website"
autoComplete="website"
value={formData.website}
onChange={handleInputChange('website')}
/>
<TextField
variant="outlined"
margin="normal"
fullWidth
id="cohort"
type="number"
inputProps={inputProps}
label="Cohort #"
name="cohort"
autoComplete="cohort"
helperText={errors.cohort}
error={errors.cohort ? true : false}
value={formData.cohort}
onChange={handleInputChange('cohort')}
/>
<TextField
variant="outlined"
margin="normal"
fullWidth
id="linkedIn"
type="linkedIn"
label="LinkedIn"
name="linkedIn"
autoComplete="linkedIn"
value={formData.linkedIn}
onChange={handleInputChange('linkedIn')}
/>
<TextField
variant="outlined"
margin="normal"
fullWidth
id="github"
type="github"
label="GitHub"
name="github"
autoComplete="github"
value={formData.github}
onChange={handleInputChange('github')}
/>
<TextField
variant="outlined"
margin="normal"
fullWidth
id="program"
type="program"
label="Program"
name="program"
helperText={errors.program}
error={errors.program ? true : false}
autoComplete="program"
value={formData.program}
onChange={handleInputChange('program')}
/>
{errors.general && (
<Typography variant="body2" className={classes.customError}>
{errors.general}
</Typography>
)}
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
className={classes.submit}
disabled={isInvalid}
>
Update User Info
{isloading && (
<CircularProgress size={30} className={classes.progress} />
)}
</Button>
</form>
</div>
<Box mt={8}>
<Copyright />
</Box>
</Container>
</div>
) : (
<CircularProgress size={60} className={classes.progressOne} />
)}
</>
)
}
export default Profile
I am fetching the token for my headers from local storage and trying to post the form data to my user endpoint. When I post, I get an error stating:
xhr.js:178 POST http://localhost:3000/[object%20Object] 404 (Not
Found)
I'm hitting the correct end point, but for some reason the post isn't hitting the /user endpoint with my form data. any insight would be awesome.
I need to insert one more field in the Login page, therefore, one other entry in the JSON login request and, although I managed to insert the field in the login page (as a FormControl with Selects), I'm having trouble in inserting the selected option into the JSON request.
login.js:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { propTypes, reduxForm, Field } from 'redux-form';
import { connect } from 'react-redux';
import compose from 'recompose/compose';
import Avatar from '#material-ui/core/Avatar';
import Button from '#material-ui/core/Button';
import Card from '#material-ui/core/Card';
import CardActions from '#material-ui/core/CardActions';
import CircularProgress from '#material-ui/core/CircularProgress';
import TextField from '#material-ui/core/TextField';
import { withStyles } from '#material-ui/core/styles';
import LockIcon from '#material-ui/icons/Lock';
import { MenuItem, Select, FormControl, InputLabel } from '#material-ui/core';
import { Notification, translate, userLogin } from 'react-admin';
import InstitutionSelect from "./InstitutionSelect";
const styles = theme => ({
main: {
display: 'flex',
flexDirection: 'column',
minHeight: '100vh',
alignItems: 'center',
justifyContent: 'flex-start',
background: 'url(https://source.unsplash.com/random/1600x900)',
backgroundRepeat: 'no-repeat',
backgroundSize: 'cover',
},
card: {
minWidth: 300,
marginTop: '6em',
},
avatar: {
margin: '1em',
display: 'flex',
justifyContent: 'center',
},
icon: {
backgroundColor: theme.palette.secondary.main,
},
hint: {
marginTop: '1em',
display: 'flex',
justifyContent: 'center',
color: theme.palette.grey[500],
},
form: {
padding: '0 1em 1em 1em',
},
input: {
marginTop: '1em',
},
actions: {
padding: '0 1em 1em 1em',
},
});
// see http://redux-form.com/6.4.3/examples/material-ui/
const renderInput = ({
meta: { touched, error } = {},
input: { ...inputProps },
...props
}) => (
<TextField
error={!!(touched && error)}
helperText={touched && error}
{...inputProps}
{...props}
fullWidth
/>
);
class Login extends Component {
login = auth =>
this.props.userLogin(
auth,
this.props.location.state
? this.props.location.state.nextPathname
: '/'
);
render() {
const { classes, handleSubmit, isLoading, translate } = this.props;
return (
<div className={classes.main}>
<Card className={classes.card}>
<div className={classes.avatar}>
<Avatar className={classes.icon}>
<LockIcon />
</Avatar>
</div>
<form onSubmit={handleSubmit(this.login)}>
<div className={classes.form}>
<div className={classes.input}>
<InstitutionSelect
name="institution"
disabled={isLoading}
/>
</div>
<div className={classes.input}>
<Field
name="username"
component={renderInput}
label={translate('ra.auth.username')}
disabled={isLoading}
/>
</div>
<div className={classes.input}>
<Field
name="password"
component={renderInput}
label={translate('ra.auth.password')}
type="password"
disabled={isLoading}
/>
</div>
</div>
<CardActions className={classes.actions}>
<Button
variant="raised"
type="submit"
color="primary"
disabled={isLoading}
className={classes.button}
fullWidth
>
{isLoading && (
<CircularProgress size={25} thickness={2} />
)}
{translate('ra.auth.sign_in')}
</Button>
</CardActions>
</form>
</Card>
<Notification />
</div>
);
}
}
Login.propTypes = {
...propTypes,
authProvider: PropTypes.func,
classes: PropTypes.object,
previousRoute: PropTypes.string,
translate: PropTypes.func.isRequired,
// Insert Institution here?
userLogin: PropTypes.func.isRequired,
};
const mapStateToProps = state => ({ isLoading: state.admin.loading > 0 });
const enhance = compose(
translate,
reduxForm({
form: 'signIn',
validate: (values, props) => {
const errors = {};
const { translate } = props;
if (!values.institution) {
errors.institution = translate('ra.validation.required');
}
if (!values.username) {
errors.username = translate('ra.validation.required');
}
if (!values.password) {
errors.password = translate('ra.validation.required');
}
return errors;
},
}),
connect(
mapStateToProps,
{ userLogin }
),
withStyles(styles)
);
export default enhance(Login);
authProvider.js:
import { AUTH_LOGIN, AUTH_LOGOUT, AUTH_ERROR, AUTH_CHECK } from 'react-admin';
export default (type, params) => {
// called when the user attempts to log in
if (type === AUTH_LOGIN) {
const { institution, username, password } = params;
localStorage.setItem('username', username);
const request = new Request('194.117.29.119:3000/api/users/login', {
method: 'POST',
body: JSON.stringify({
institution,
username,
password
}),
headers: new Headers({ 'Content-Type': 'application/json' }),
})
return fetch(request)
.then(response => {
if (response.status < 200 || response.status >= 300) {
throw new Error(response.statusText);
}
return response.json();
})
.then(({ token }) => {
localStorage.setItem('token', token);
});
}
// called when the user clicks on the logout button
if (type === AUTH_LOGOUT) {
localStorage.removeItem('username');
return Promise.resolve();
}
// called when the API returns an error
if (type === AUTH_ERROR) {
const { status } = params;
if (status === 401 || status === 403) {
localStorage.removeItem('username');
return Promise.reject();
}
return Promise.resolve();
}
// called when the user navigates to a new location
if (type === AUTH_CHECK) {
return localStorage.getItem('username')
? Promise.resolve()
: Promise.reject();
}
return Promise.reject('Unknown method');
};
InstitutionSelect.js:
import React, { Component } from "react";
import MenuItem from "#material-ui/core/MenuItem";
import Select from "#material-ui/core/Select";
import { withStyles } from '#material-ui/core/styles';
import MuiThemeProvider from "#material-ui/core/styles/MuiThemeProvider";
import { InputLabel, FormControl } from "#material-ui/core";
const styles = theme => ({
fullWidth: {
width: "100%"
}
});
class InstitutionSelect extends Component {
constructor(props) {
super(props);
this.state = {
selectOptions: [
{
value: "blablabla",
id: "1"
},
],
selectedValue: "",
};
}
renderSelectOptions = () => {
return this.state.selectOptions.map((option, i) => (
<MenuItem key={option.id} value={option.id}>
{option.value}
</MenuItem>
));
};
handleChange = event => {
this.setState({ selectedValue: event.target.value });
};
render() {
const { classes } = this.props;
return (
<MuiThemeProvider>
<FormControl className={classes.fullWidth}>
<InputLabel>Institution</InputLabel>
<Select value={this.state.selectedValue} onChange={this.handleChange} >
{this.renderSelectOptions()}
</Select>
</FormControl>
</MuiThemeProvider>
);
}
}
export default withStyles(styles)(InstitutionSelect);