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
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 was making a dialog which has two tabs named Address and Map, which allows the user to enter city name and street name in two different textfields in address tab and be able to pinpoint or auto-locate the location in the map. In map tab I was using react-leaflet map to show the map itself to the user, so far so good but after switching between tabs the map changes to a monotonic gray image. zoom in and out won't help it!
Code:
import * as React from 'react';
import { useEffect, useState } from 'react';
import Button from '#mui/material/Button';
import Dialog from '#mui/material/Dialog';
import DialogActions from '#mui/material/DialogActions';
import DialogContent from '#mui/material/DialogContent';
import DialogContentText from '#mui/material/DialogContentText';
import DialogTitle from '#mui/material/DialogTitle';
import useMediaQuery from '#mui/material/useMediaQuery';
import CloseIcon from '#mui/icons-material/CloseOutlined';
import { Divider, IconButton, InputLabel, } from '#mui/material';
import { Box, Grid, Tab, TextField, useTheme } from '#material-ui/core';
import { TabContext, TabList, TabPanel } from '#mui/lab';
import "leaflet/dist/leaflet.css";
import icon from "../../../../Account/components/constants";
import { MapContainer, TileLayer, Marker, useMapEvents, } from 'react-leaflet'
const useGeoLocation = () => {
// this function will allow the user to get the current location of the device!
const [location, setLocation] = useState({
loaded: false,
coordinates: { lat: "", lng: "" }
});
const onSuccess = (location) => {
setLocation({
loaded: true,
coordinates: {
lat: location.coords.latitude,
lng: location.coords.longitude,
}
});
};
const onError = (error) => {
setLocation({
loaded: true,
error,
});
};
useEffect(() => {
if (!("geolocation" in navigator)) {
onError({
code: 0,
message: "Your device GPS is OFF!",
});
}
navigator.geolocation.getCurrentPosition(onSuccess, onError);
}, []);
return location;
}
export default function AddressDialog() {
// Genral Properties!
const [open, setOpen] = useState(false);
const theme = useTheme();
const fullScreen = useMediaQuery(theme.breakpoints.down('sm'));
const [value, setValue] = useState(0);
// Address Tab Properties!
const [city, setCity] = useState("");
const [street, setStreet] = useState();
// Map Properties!
const [initializerPos,] = useState([40.689247, -74.044502]);
const [position, setPosition] = useState(initializerPos);
const [mapState, setMapState] = useState({
position: position,
map: null
});
const zoom_level = 18;
const location = useGeoLocation();
// Arrow funcitons!
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
setValue(0);
};
const handleSubmit = () => {
// api update in here
}
const showMyLocation = () => {
if (location.loaded && !location.error) {
let pos = [location.coordinates.lat, location.coordinates.lng];
setPosition([location.coordinates.lat, location.coordinates.lng]);
setMapState((state) => ({
...state,
position: pos,
}));
const { map } = mapState;
if (map) {
map.flyTo(pos, zoom_level);
}
} else {
if (location.error) {
alert(location.error.message)
}
else {
alert("Problem in loading curent location!")
}
}
};
function AddMarkerToClick() {
const [markers, setMarkers] = useState([]);
useMapEvents({
click(e) {
const newMarker = e.latlng
setMarkers([...markers, newMarker]);
setPosition([e.latlng.lat, e.latlng.lng]);
setMapState((state) => ({
...state,
position: newMarker,
}));
const { map } = mapState;
if (map)
{
map.flyTo(newMarker, zoom_level);
}
},
});
return null
};
return (
<div dir="ltr">
<Button onClick={handleClickOpen} variant="contained" type="button" aria-label="Edit Info" fullWidth size="small">
Edit Address Info
</Button>
<Dialog fullScreen={fullScreen} open={open} aria-labelledby="responsive-dialog-title">
<DialogTitle>
<IconButton onClick={handleClose} aria-label="Close Dialog">
<CloseIcon fontSize="medium" />
</IconButton>
</DialogTitle>
<Divider />
<DialogTitle>Edit Address</DialogTitle>
<DialogContent id ="dialogContent" >
<DialogContentText>
In this section you are able to edit your address info
</DialogContentText>
<TabContext value={value.toString()} >
<Box >
<TabList
onChange={(event, newValue) => { setValue(parseInt(newValue, 10));}}
aria-label="address-map-tab">
<Tab label="Address" value="0" />
<Tab label="Map" value="1" />
</TabList>
</Box>
<TabPanel value="0">
<Grid container spacing={theme.spacing(0)}
>
<Grid item xs={12} sm={6}>
<TextField value={city} onChange={(e) => setCity(e.target.value)} margin="normal" variant="outlined"
required
fullWidth
type="text"
name="area"
id="area"
label={"city"}
placeholder={"ex: New York"}
/>
</Grid>
<Grid item xs={12} sm={6}>
<TextField
value={street}
onChange={(e) => setStreet(e.target.value)}
margin="normal"
variant="outlined"
required
fullWidth
type="text"
name="street"
id="street"
label={"Streen Name"}
placeholder={"ex: wall street"}
/>
</Grid>
</Grid>
</TabPanel>
<TabPanel value="1">
<Grid container>
<div style={{
marginLeft: "auto",
marginRight: "auto",
width: "100%"
}}>
<InputLabel>
Your location in map:
</InputLabel>
<MapContainer
center={mapState.position}
zoom ={15}
scrollWheelZoom
style={{
height: fullScreen ? 200 : 350,
width: fullScreen ? "100%" : "100%",
textAlign: "center",
marginLeft: "auto",
marginRight: "auto",
marginTop: theme.spacing(1)
}}
whenCreated={map => setMapState({ map })}
>
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<AddMarkerToClick />
{
position && (
<Marker position={position} icon={icon}></Marker>
)
}
</MapContainer>
<Button variant="outlined" color="primary" size="large" onClick={showMyLocation}>
Here!
</Button>
</div>
</Grid>
</TabPanel>
</TabContext>
</DialogContent>
<DialogActions style={{ marginLeft: theme.spacing(2), marginRight: theme.spacing(2) }}>
<Grid container direction="row" spacing={1}>
<Grid item container xs={4} dir="left" justifyContent="flex-end">
<Button variant="contained" type="button" color="error" fullWidth
name="cancel-btn" onClick={handleClose}
>
Cancel
</Button>
</Grid>
<Grid item container xs={8} >
<Button variant="contained" type="button" color="primary" fullWidth
name="submit-btn" onClick={handleSubmit} >
Save
</Button>
</Grid>
</Grid>
</DialogActions>
</Dialog>
</div>
);
After switching between tabs, the map grayed out, and shows nothing!
Then interacting with map, e.g, clicking on it, zoom in or out results the error!
Uncaught Error: Set map center and zoom first.
My Solution:
What I did is to save the current (after first interaction with map) state of the map and then try to restart it from saved state for future use (switching to other tabs/views).
To save the state of the map, It should happen right exactly before switching out from the map view! In this case before switching back to address tab or closing The Dialog.
Fixed Code:
import * as React from 'react';
import { useEffect, useState } from 'react';
import Button from '#mui/material/Button';
import Dialog from '#mui/material/Dialog';
import DialogActions from '#mui/material/DialogActions';
import DialogContent from '#mui/material/DialogContent';
import DialogContentText from '#mui/material/DialogContentText';
import DialogTitle from '#mui/material/DialogTitle';
import useMediaQuery from '#mui/material/useMediaQuery';
import CloseIcon from '#mui/icons-material/CloseOutlined';
import { Divider, IconButton, InputLabel, } from '#mui/material';
import { Box, Grid, Tab, TextField, useTheme } from '#material-ui/core';
import { TabContext, TabList, TabPanel } from '#mui/lab';
import "leaflet/dist/leaflet.css";
import icon from "../../../../Account/components/constants";
import { MapContainer, TileLayer, Marker, useMapEvents, } from 'react-leaflet'
const useGeoLocation = () => {
// this function will allow the user to get the current location of the device!
const [location, setLocation] = useState({
loaded: false,
coordinates: { lat: "", lng: "" }
});
const onSuccess = (location) => {
setLocation({
loaded: true,
coordinates: {
lat: location.coords.latitude,
lng: location.coords.longitude,
}
});
};
const onError = (error) => {
setLocation({
loaded: true,
error,
});
};
useEffect(() => {
if (!("geolocation" in navigator)) {
onError({
code: 0,
message: "Your device GPS is OFF!",
});
}
navigator.geolocation.getCurrentPosition(onSuccess, onError);
}, []);
return location;
}
export default function AddressDialog() {
// Genral Properties!
const [open, setOpen] = useState(false);
const theme = useTheme();
const fullScreen = useMediaQuery(theme.breakpoints.down('sm'));
const [value, setValue] = useState(0);
// Address Tab Properties!
const [city, setCity] = useState("");
const [street, setStreet] = useState();
// Map Properties!
const [initializerPos,] = useState([40.689247, -74.044502]);
const [position, setPosition] = useState(initializerPos);
const [mapState, setMapState] = useState({
position: position,
map: null
});
const zoom_level = 18;
const location = useGeoLocation();
// Two new properties to overcome the gray out problem.
const initializeZoom_level = 15;
const [mapZoom, setMapZoom] = useState(initializeZoom_level);
// Arrow funcitons!
const stabilizeMapData = (isItADialogClose ) => {
setMapZoom(isItADialogClose ? initializeZoom_level : mapZoom);
setPosition(isItADialogClose ? initializerPos : position);
setMapState({
position: isItADialogClose ? initializerPos : position,
map: null
});
}
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
stabilizeMapData(true); // The user is about to close The Dialog!
setOpen(false);
setValue(0);
};
const handleSubmit = () => {
// api update in here
}
const showMyLocation = () => {
if (location.loaded && !location.error) {
let pos = [location.coordinates.lat, location.coordinates.lng];
setPosition([location.coordinates.lat, location.coordinates.lng]);
setMapState((state) => ({
...state,
position: pos,
}));
const { map } = mapState;
if (map) {
map.flyTo(pos, zoom_level);
setMapZoom(zoom_level);
}
} else {
if (location.error) {
alert(location.error.message)
}
else {
alert("Problem in loading curent location!")
}
}
};
function AddMarkerToClick() {
const [markers, setMarkers] = useState([]);
useMapEvents({
click(e) {
const newMarker = e.latlng
setMarkers([...markers, newMarker]);
setPosition([e.latlng.lat, e.latlng.lng]);
setMapState((state) => ({
...state,
position: newMarker,
}));
const { map } = mapState;
if (map)
{
map.flyTo(newMarker, zoom_level);
setMapZoom(zoom_level);
}
},
});
return null
};
return (
<div dir="ltr">
<Button onClick={handleClickOpen} variant="contained" type="button" aria-label="Edit Info" fullWidth size="small">
Edit Address Info
</Button>
<Dialog fullScreen={fullScreen} open={open} aria-labelledby="responsive-dialog-title">
<DialogTitle>
<IconButton onClick={handleClose} aria-label="Close Dialog">
<CloseIcon fontSize="medium" />
</IconButton>
</DialogTitle>
<Divider />
<DialogTitle>Edit Address</DialogTitle>
<DialogContent id ="dialogContent" >
<DialogContentText>
In this section you are able to edit your address info
</DialogContentText>
<TabContext value={value.toString()} >
<Box >
<TabList
onChange={(event, newValue) => {
if (value === 1) {
// if it is in The Map Tab and It's about to switch the tab!
stabilizeMapData(false);
}
setValue(parseInt(newValue, 10));
}}
aria-label="address-map-tab">
<Tab label="Address" value="0" />
<Tab label="Map" value="1" />
</TabList>
</Box>
<TabPanel value="0">
<Grid container spacing={theme.spacing(0)}
>
<Grid item xs={12} sm={6}>
<TextField value={city} onChange={(e) => setCity(e.target.value)} margin="normal" variant="outlined"
required
fullWidth
type="text"
name="area"
id="area"
label={"city"}
placeholder={"ex: New York"}
/>
</Grid>
<Grid item xs={12} sm={6}>
<TextField
value={street}
onChange={(e) => setStreet(e.target.value)}
margin="normal"
variant="outlined"
required
fullWidth
type="text"
name="street"
id="street"
label={"Streen Name"}
placeholder={"ex: wall street"}
/>
</Grid>
</Grid>
</TabPanel>
<TabPanel value="1">
<Grid container>
<div style={{
marginLeft: "auto",
marginRight: "auto",
width: "100%"
}}>
<InputLabel>
Your location in map:
</InputLabel>
<MapContainer
center={mapState.position}
zoom={mapZoom}
scrollWheelZoom
style={{
height: fullScreen ? 200 : 350,
width: fullScreen ? "100%" : "100%",
textAlign: "center",
marginLeft: "auto",
marginRight: "auto",
marginTop: theme.spacing(1)
}}
whenCreated={map => setMapState({ map })}
>
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<AddMarkerToClick />
{
position && (
<Marker position={position} icon={icon}></Marker>
)
}
</MapContainer>
<Button variant="outlined" color="primary" size="large" onClick={showMyLocation}>
Here!
</Button>
</div>
</Grid>
</TabPanel>
</TabContext>
</DialogContent>
<DialogActions style={{ marginLeft: theme.spacing(2), marginRight: theme.spacing(2) }}>
<Grid container direction="row" spacing={1}>
<Grid item container xs={4} dir="left" justifyContent="flex-end">
<Button variant="contained" type="button" color="error" fullWidth
name="cancel-btn" onClick={handleClose}
>
Cancel
</Button>
</Grid>
<Grid item container xs={8} >
<Button variant="contained" type="button" color="primary" fullWidth
name="submit-btn" onClick={handleSubmit} >
Save
</Button>
</Grid>
</Grid>
</DialogActions>
</Dialog>
</div>
);
}
I did some comments in the code to demonstrate the solution. If you know a better solution to this problem I looking forward to see it.
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",
},
}
)
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.