Skip to content

Commit

Permalink
Merge pull request #385 from reactioncommerce/akarshit-feat-account-j…
Browse files Browse the repository at this point in the history
…s-auth

feat: use account-js for authentication"
  • Loading branch information
Akarshit authored Aug 25, 2021
2 parents 652340d + c82dd3e commit deea754
Show file tree
Hide file tree
Showing 23 changed files with 1,088 additions and 374 deletions.
3 changes: 0 additions & 3 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@ MONGO_OPLOG_URL=mongodb://mongo.reaction.localhost:27017/local
MONGO_URL=mongodb://mongo.reaction.localhost:27017/reaction
NODE_ENV=development
PORT=4080
OAUTH2_ADMIN_URL=http://hydra.reaction.localhost:4445
OAUTH2_IDP_PUBLIC_CHANGE_PASSWORD_URL=http://localhost:4100/account/change-password?email=EMAIL&from=FROM
OAUTH2_PUBLIC_URL=http://localhost:4444
PUBLIC_GRAPHQL_API_URL_HTTP=http://localhost:3000/graphql
PUBLIC_GRAPHQL_API_URL_WS=ws://localhost:3000/graphql
PUBLIC_FILES_BASE_URL=http://localhost:3000
Expand Down
178 changes: 178 additions & 0 deletions imports/client/ui/components/Entry/AccountDropdown.js
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;
84 changes: 84 additions & 0 deletions imports/client/ui/components/Entry/ChangePassword.js
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
};
91 changes: 91 additions & 0 deletions imports/client/ui/components/Entry/Entry.js
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);
Loading

0 comments on commit deea754

Please sign in to comment.