-
Notifications
You must be signed in to change notification settings - Fork 82
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #385 from reactioncommerce/akarshit-feat-account-j…
…s-auth feat: use account-js for authentication"
- Loading branch information
Showing
23 changed files
with
1,088 additions
and
374 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
import React, { useState, Fragment, useEffect } from "react"; | ||
import PropTypes from "prop-types"; | ||
import { makeStyles } from "@material-ui/core/styles"; | ||
import { useLocation } from "react-router-dom"; | ||
import IconButton from "@material-ui/core/IconButton"; | ||
import Button from "@material-ui/core/Button"; | ||
import ButtonBase from "@material-ui/core/ButtonBase"; | ||
import AccountIcon from "mdi-material-ui/Account"; | ||
import Popover from "@material-ui/core/Popover"; | ||
import { i18next } from "/client/api"; | ||
import useCurrentShopId from "/imports/client/ui/hooks/useCurrentShopId.js"; | ||
import ViewerInfo from "@reactioncommerce/components/ViewerInfo/v1"; | ||
import Link from "@material-ui/core/Link"; | ||
import Modal from "@material-ui/core/Modal"; | ||
import getAccountsHandler from "../../../../../lib/accountsServer"; | ||
import Login from "./Login"; | ||
import SignUp from "./SignUp"; | ||
import ChangePassword from "./ChangePassword"; | ||
import ForgotPassword from "./ForgotPassword"; | ||
import ResetPassword from "./ResetPassword"; | ||
|
||
/** | ||
* function to get query params passed to the current URL | ||
* @returns {Object} query params of the URL | ||
*/ | ||
function useQuery() { | ||
return new URLSearchParams(useLocation().search); // eslint-disable-line node/no-unsupported-features/node-builtins | ||
} | ||
|
||
const useStyles = makeStyles((theme) => ({ | ||
accountDropdown: { | ||
width: 320, | ||
padding: theme.spacing(2) | ||
}, | ||
marginBottom: { | ||
marginBottom: theme.spacing(2) | ||
}, | ||
paper: { | ||
position: "absolute", | ||
width: 380, | ||
backgroundColor: theme.palette.background.paper, | ||
border: "2px solid #000", | ||
boxShadow: theme.shadows[5], | ||
padding: theme.spacing(2, 4, 3), | ||
top: "50%", | ||
left: "50%", | ||
transform: "translate(-50%, -50%)", | ||
display: "flex", | ||
alignItems: "center", | ||
justifyContent: "center" | ||
} | ||
})); | ||
|
||
const AccountDropdown = (props) => { | ||
const { viewer, refetchViewer } = props; | ||
const [shopId] = useCurrentShopId(); | ||
const query = useQuery(); | ||
const resetToken = query.get("resetToken"); | ||
const classes = useStyles(); | ||
const [open, setOpen] = React.useState(false); | ||
const [anchorElement, setAnchorElement] = useState(null); | ||
const [modalValue, setModalValue] = useState(""); | ||
const { accountsClient } = getAccountsHandler(); | ||
const isAuthenticated = viewer && viewer._id; | ||
|
||
const onClose = () => { | ||
setAnchorElement(null); | ||
}; | ||
|
||
useEffect(() => { | ||
if (!resetToken) return; | ||
setOpen(true); | ||
setModalValue("reset-password"); | ||
}, [resetToken]); | ||
|
||
const openModal = (value) => { | ||
setModalValue(value); | ||
setOpen(true); | ||
onClose(); | ||
}; | ||
|
||
const closeModal = () => { | ||
setOpen(false); | ||
}; | ||
|
||
const handleSignOut = async () => { | ||
await accountsClient.logout(); | ||
await refetchViewer(); | ||
onClose(); | ||
}; | ||
|
||
const toggleOpen = (event) => { | ||
setAnchorElement(event.currentTarget); | ||
}; | ||
|
||
const getModalComponent = () => { | ||
let comp = Login; | ||
if (modalValue === "signup") { | ||
comp = SignUp; | ||
} else if (modalValue === "change-password") { | ||
comp = ChangePassword; | ||
} else if (modalValue === "forgot-password") { | ||
comp = ForgotPassword; | ||
} else if (modalValue === "reset-password") { | ||
comp = ResetPassword; | ||
} | ||
return React.createElement(comp, { closeModal, openModal, resetToken, refetch: refetchViewer }); | ||
}; | ||
|
||
return ( | ||
<Fragment> | ||
<Modal open={open} onClose={closeModal} aria-labelledby="entry-modal" aria-describedby="entry-modal"> | ||
<div className={classes.paper}>{getModalComponent()}</div> | ||
</Modal> | ||
{isAuthenticated ? ( | ||
<ButtonBase onClick={toggleOpen}> | ||
<ViewerInfo viewer={viewer} /> | ||
</ButtonBase> | ||
) : ( | ||
<IconButton color="primary" onClick={toggleOpen}> | ||
<AccountIcon /> | ||
</IconButton> | ||
)} | ||
|
||
<Popover | ||
anchorEl={anchorElement} | ||
anchorOrigin={{ | ||
vertical: "bottom", | ||
horizontal: "center" | ||
}} | ||
open={Boolean(anchorElement)} | ||
onClose={onClose} | ||
> | ||
<div className={classes.accountDropdown}> | ||
{isAuthenticated ? ( | ||
<Fragment> | ||
<div className={classes.marginBottom}> | ||
<Link href={`/${shopId}/profile`}> | ||
<Button color="primary" fullWidth> | ||
{i18next.t("admin.userAccountDropdown.profileLabel")} | ||
</Button> | ||
</Link> | ||
</div> | ||
<div className={classes.marginBottom}> | ||
<Button color="primary" fullWidth onClick={() => openModal("change-password")}> | ||
Change Password | ||
</Button> | ||
</div> | ||
<Button color="primary" fullWidth onClick={handleSignOut} variant="contained"> | ||
Sign Out | ||
</Button> | ||
</Fragment> | ||
) : ( | ||
<Fragment> | ||
<div className={classes.authContent}> | ||
<Button color="primary" fullWidth variant="contained" onClick={() => openModal("login")}> | ||
Sign In | ||
</Button> | ||
</div> | ||
<Button color="primary" fullWidth onClick={() => openModal("signup")}> | ||
Create Account | ||
</Button> | ||
</Fragment> | ||
)} | ||
</div> | ||
</Popover> | ||
</Fragment> | ||
); | ||
}; | ||
|
||
AccountDropdown.propTypes = { | ||
refetchViewer: PropTypes.func, | ||
viewer: PropTypes.shape({ | ||
_id: PropTypes.string | ||
}) | ||
}; | ||
|
||
export default AccountDropdown; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
import React, { useState } from "react"; | ||
import PropTypes from "prop-types"; | ||
import { makeStyles } from "@material-ui/core/styles"; | ||
import FormControl from "@material-ui/core/FormControl"; | ||
import InputLabel from "@material-ui/core/InputLabel"; | ||
import Input from "@material-ui/core/Input"; | ||
import Button from "@material-ui/core/Button"; | ||
import red from "@material-ui/core/colors/red"; | ||
|
||
import getAccountsHandler from "../../../../../lib/accountsServer"; | ||
import hashPassword from "../../../../../lib/utils/hashPassword"; | ||
|
||
|
||
const useStyles = makeStyles((theme) => ({ | ||
root: { | ||
"display": "flex", | ||
"flexDirection": "column", | ||
"& > *": { | ||
margin: theme.spacing(1) | ||
} | ||
}, | ||
error: { | ||
marginTop: theme.spacing(2), | ||
color: red[500], | ||
fontSize: "1.1em", | ||
textAlign: "center" | ||
}, | ||
resetButton: { | ||
marginTop: theme.spacing(4) | ||
} | ||
})); | ||
|
||
/** | ||
* | ||
* @param {Object} props of the structure { closeModal: function called to close the modal } | ||
* @returns {Object} the component to render for updating password | ||
*/ | ||
export default function ChangePassword(props) { | ||
const { closeModal } = props; | ||
const classes = useStyles(); | ||
const [oldPassword, setOldPassword] = useState(""); | ||
const [newPassword, setNewPassword] = useState(""); | ||
const [error, setError] = useState(""); | ||
const { passwordClient } = getAccountsHandler(); | ||
|
||
const handleOldPasswordChange = (event) => { | ||
setOldPassword(event.target.value); | ||
}; | ||
const handleNewPasswordChange = (event) => { | ||
setNewPassword(event.target.value); | ||
}; | ||
|
||
const handleChangePassword = async () => { | ||
try { | ||
await passwordClient.changePassword(hashPassword(oldPassword), hashPassword(newPassword)); | ||
closeModal(); | ||
} catch (err) { | ||
setError(err.message); | ||
} | ||
}; | ||
return ( | ||
<form className={classes.root} noValidate> | ||
<h1>Change password</h1> | ||
<FormControl> | ||
<InputLabel htmlFor="old-password">Old Password</InputLabel> | ||
<Input id="old-password" aria-describedby="old-password" onChange={handleOldPasswordChange} value={oldPassword} | ||
type="password" | ||
/> | ||
</FormControl> | ||
<FormControl> | ||
<InputLabel htmlFor="new-password">New Password</InputLabel> | ||
<Input id="new-password" aria-describedby="new-password" onChange={handleNewPasswordChange} value={newPassword} | ||
type="password" | ||
/> | ||
</FormControl> | ||
{!!error && <div className={classes.error}>{error}</div>} | ||
<Button className={classes.resetButton} onClick={handleChangePassword} color="primary" variant="contained">Change</Button> | ||
</form> | ||
); | ||
} | ||
|
||
ChangePassword.propTypes = { | ||
closeModal: PropTypes.func | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
import React from "react"; | ||
import PropTypes from "prop-types"; | ||
import Router from "translations/i18nRouter"; | ||
import { withStyles } from "@material-ui/core/styles"; | ||
import Typography from "@material-ui/core/Typography"; | ||
import Grid from "@material-ui/core/Grid"; | ||
import GuestForm from "@reactioncommerce/components/GuestForm/v1"; | ||
import Button from "@reactioncommerce/components/Button/v1"; | ||
|
||
// flex wrapper jss mixin | ||
const flexWrapper = () => ({ | ||
alignItems: "stretch", | ||
display: "flex", | ||
flexDirection: "column", | ||
justifyContent: "flex-start" | ||
}); | ||
|
||
const styles = (theme) => ({ | ||
loginWrapper: { | ||
...flexWrapper(), | ||
paddingBottom: theme.spacing(8), | ||
[theme.breakpoints.up("md")]: { | ||
minHeight: "400px", | ||
paddingBottom: 0, | ||
paddingRight: theme.spacing(8) | ||
} | ||
}, | ||
loginButton: { | ||
marginTop: theme.spacing(3) | ||
}, | ||
guestWrapper: { | ||
...flexWrapper(), | ||
borderTop: `solid 1px ${theme.palette.reaction.black10}`, | ||
paddingTop: theme.spacing(8), | ||
[theme.breakpoints.up("md")]: { | ||
borderLeft: `solid 1px ${theme.palette.reaction.black10}`, | ||
borderTop: "none", | ||
paddingLeft: theme.spacing(8), | ||
paddingTop: 0 | ||
} | ||
} | ||
}); | ||
|
||
const Entry = (props) => { | ||
const { classes, onLoginButtonClick, onRegisterButtonClick, setEmailOnAnonymousCart } = props; | ||
return ( | ||
<Grid container> | ||
<Grid item xs={12} md={7}> | ||
<div className={classes.loginWrapper}> | ||
<Typography variant="h6" gutterBottom> | ||
Returning Customer | ||
</Typography> | ||
<Button onClick={onLoginButtonClick} actionType="important" isFullWidth className={classes.loginButton}> | ||
Login | ||
</Button> | ||
<Button onClick={onRegisterButtonClick} actionType="secondary" isFullWidth className={classes.loginButton}> | ||
Create a new accounts | ||
</Button> | ||
</div> | ||
</Grid> | ||
<Grid item xs={12} md={5}> | ||
<div className={classes.guestWrapper}> | ||
<Typography variant="h6" gutterBottom> | ||
Guest Checkout | ||
</Typography> | ||
<GuestForm onSubmit={setEmailOnAnonymousCart} /> | ||
</div> | ||
</Grid> | ||
</Grid> | ||
); | ||
}; | ||
|
||
Entry.defaultProps = { | ||
onLoginButtonClick() { | ||
Router.push("/signin"); | ||
}, | ||
onRegisterButtonClick() { | ||
Router.push("/signup"); | ||
}, | ||
setEmailOnAnonymousCart() {} | ||
}; | ||
|
||
Entry.propTypes = { | ||
classes: PropTypes.object, | ||
onLoginButtonClick: PropTypes.func, | ||
onRegisterButtonClick: PropTypes.func, | ||
setEmailOnAnonymousCart: PropTypes.func, | ||
theme: PropTypes.object | ||
}; | ||
|
||
export default withStyles(styles, { withTheme: true })(Entry); |
Oops, something went wrong.