Merge pull request #107 from bluewave-labs/feat/registration-flow

Implemented registering, resolves #104
This commit is contained in:
Veysel
2024-06-07 17:00:34 -04:00
committed by GitHub
8 changed files with 719 additions and 142 deletions
+539 -69
View File
File diff suppressed because it is too large Load Diff
+2
View File
@@ -21,6 +21,8 @@
"@reduxjs/toolkit": "2.2.5",
"axios": "1.7.2",
"dayjs": "1.11.11",
"joi": "17.13.1",
"jwt-decode": "^4.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-redux": "9.1.2",
+12
View File
@@ -3,12 +3,24 @@ import { Outlet } from "react-router";
import DashboardSidebar from "../../Components/DashboardSidebar";
import "./index.css";
// Demo, remove me
import { jwtDecode } from "jwt-decode";
// Emd Demo
const HomeLayout = () => {
const token = localStorage.getItem("token");
const decodedToken = token && jwtDecode(token);
return (
<div className="home-layout">
<DashboardSidebar />
<div className="main-content">
<NavBar />
{/* Remove me later */}
{token && <p>You are logged in</p>}
{token && <p>First Name: {decodedToken.firstname}</p>}
{token && <p>Last Name: {decodedToken.lastname}</p>}
{token && <p>Email: {decodedToken.email}</p>}
{/* I am a demo */}
</div>
<Outlet />
</div>
+1
View File
@@ -5,6 +5,7 @@ import DashboardMenu from "../../Components/DashboardMenu";
import Integrations from "../../Components/Integrations";
const Home = () => {
const token = localStorage.getItem("token");
return (
<>
<div>Home</div>
+125 -68
View File
@@ -1,4 +1,7 @@
import React, { useState } from "react";
import { useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import axios from "axios";
import "./index.css";
import BackgroundPattern from "../../Components/BackgroundPattern/BackgroundPattern";
import Logomark from "../../assets/Images/Logomark.png";
@@ -8,8 +11,14 @@ import StringTextField from "../../Components/TextFields/Text/TextField";
import Check from "../../Components/Check/Check";
import Button from "../../Components/Button";
import Google from "../../assets/Images/Google.png";
import { registerValidation } from "../../Validation/validation";
const BASE_URL = import.meta.env.VITE_APP_API_BASE_URL;
const Register = () => {
const navigate = useNavigate();
// TODO If possible, change the IDs of these fields to match the backend
const idMap = {
"register-firstname-input": "firstname",
"register-lastname-input": "lastname",
@@ -17,6 +26,8 @@ const Register = () => {
"register-password-input": "password",
};
const [errors, setErrors] = useState({});
const [form, setForm] = useState({
firstname: "",
lastname: "",
@@ -24,82 +35,128 @@ const Register = () => {
password: "",
});
const handleInput = (e) => {
const fieldName = idMap[e.target.id];
setForm({
...form,
[idMap[e.target.id]]: e.target.value,
useEffect(() => {
const { error } = registerValidation.validate(form, {
abortEarly: false,
});
if (error) {
// Creates an error object in the format { field: message }
const validationErrors = error.details.reduce((acc, err) => {
return { ...acc, [err.path[0]]: err.message };
}, {});
setErrors(validationErrors);
}
}, []);
const handleInput = (e) => {
const newForm = { ...form, [idMap[e.target.id]]: e.target.value };
setForm(newForm);
};
const handleSubmit = async () => {
console.log("submitting");
try {
await registerValidation.validateAsync(form, { abortEarly: false });
const result = await axios.post(`${BASE_URL}/auth/register`, form);
const token = result.data.data;
localStorage.setItem("token", token);
navigate("/"); // Redirect to home page
} catch (error) {
// TODO User friendly error display
if (error.name === "ValidationError") {
console.log(error);
alert("Invalid input");
} else if (error.response) {
console.log(error);
alert(error.response.data.msg);
} else {
console.log(error);
alert("Unknown error");
}
}
};
return (
<div className="register-page">
<BackgroundPattern></BackgroundPattern>
<div className="register-form">
<div className="register-form-header">
<img
className="register-form-header-logo"
src={Logomark}
alt="Logomark"
/>
<div className="register-form-v-spacing-large" />
<div className="register-form-heading">Create an account</div>
<div className="register-form-v-spacing-large"></div>
</div>
<div className="register-form-v-spacing-40px" />
<div className="register-form-inputs">
<StringTextField
onChange={handleInput}
error={false}
label="First name*"
placeholder="Enter your first name"
id="register-firstname-input"
/>
<form>
<div className="register-form">
<div className="register-form-header">
<img
className="register-form-header-logo"
src={Logomark}
alt="Logomark"
/>
<div className="register-form-v-spacing-large" />
<div className="register-form-heading">Create an account</div>
<div className="register-form-v-spacing-large"></div>
</div>
<div className="register-form-v-spacing-40px" />
<div className="register-form-inputs">
<StringTextField
onChange={handleInput}
error={errors.firstname ? true : false}
helperText={errors.firstname ? errors.firstname : ""}
label="First name*"
placeholder="Enter your first name"
id="register-firstname-input"
/>
<div className="login-form-v2-spacing" />
<StringTextField
onChange={handleInput}
error={errors.lastname ? true : false}
helperText={errors.lastname ? errors.lastname : ""}
label="Last name*"
placeholder="Enter your last name"
id="register-lastname-input"
/>
<div className="login-form-v2-spacing" />
<EmailTextField
onChange={handleInput}
label="Email*"
error={errors.email ? true : false}
helperText={errors.email ? errors.email : ""}
placeholder="Enter your email"
id="register-email-input"
/>
<div className="login-form-v2-spacing" />
<PasswordTextField
onChange={handleInput}
label="Password*"
error={errors.password ? true : false}
helperText={errors.password ? errors.password : ""}
placeholder="Create a password"
id="register-password-input"
/>
</div>
<div className="login-form-v2-spacing" />
<StringTextField
onChange={handleInput}
error={false}
label="Last name*"
placeholder="Enter your last name"
id="register-lastname-input"
/>
<div className="register-form-checks">
<Check text="Must be at least 8 characters" />
<div className="register-form-v-spacing-small"></div>
<Check text="Must contain one special character" />
</div>
<div className="login-form-v2-spacing" />
<EmailTextField
onChange={handleInput}
label="Email*"
error={false}
placeholder="Enter your email"
id="register-email-input"
/>
<div className="login-form-v2-spacing" />
<PasswordTextField
onChange={handleInput}
label="Password*"
error={false}
placeholder="Create a password"
id="register-password-input"
/>
<div className="register-form-actions">
<Button
onClick={handleSubmit}
level="primary"
label="Get started"
sx={{ width: "100%" }}
/>
<div className="login-form-v-spacing" />
<Button
disabled={true}
level="secondary"
label="Sign up with Google"
sx={{ width: "100%", color: "#344054", fontWeight: "700" }}
img={<img className="google-enter" src={Google} alt="Google" />}
/>
</div>
</div>
<div className="login-form-v2-spacing" />
<div className="register-form-checks">
<Check text="Must be at least 8 characters" />
<div className="register-form-v-spacing-small"></div>
<Check text="Must contain one special character" />
</div>
<div className="login-form-v2-spacing" />
<div className="register-form-actions">
<Button level="primary" label="Get started" sx={{ width: "100%" }} />
<div className="login-form-v-spacing" />
<Button
disabled={true}
level="secondary"
label="Sign up with Google"
sx={{ width: "100%", color: "#344054", fontWeight: "700" }}
img={<img className="google-enter" src={Google} alt="Google" />}
/>
</div>
</div>
<div className="register-bottom-spacing"></div>
<div className="register-bottom-spacing"></div>
</form>
</div>
);
};
+27
View File
@@ -0,0 +1,27 @@
import joi from "joi";
const registerValidation = joi.object({
firstname: joi.string().required().messages({
"string.empty": "First name is required",
}),
lastname: joi.string().required().messages({
"string.empty": "Last name is required",
}),
email: joi
.string()
.email({ tlds: { allow: false } })
.required()
.messages({
"string.email": "Email must be a valid email",
"string.empty": "Email is required",
}),
password: joi.string().min(8).required().messages({
"string.min": "Password must be at least 8 characters",
"string.empty": "Password is required",
}),
});
export { registerValidation };
+12 -5
View File
@@ -9,8 +9,10 @@ const logger = require("../utils/logger");
require("dotenv").config();
var jwt = require("jsonwebtoken");
const SERVICE_NAME = "auth";
const { sendEmail } = require('../utils/sendEmail')
const { registerTemplate } = require('../utils/emailTemplates/registerTemplate')
const { sendEmail } = require("../utils/sendEmail");
const {
registerTemplate,
} = require("../utils/emailTemplates/registerTemplate");
/**
* Creates and returns JWT token with an arbitrary payload
@@ -60,10 +62,15 @@ const registerController = async (req, res, next) => {
userId: newUser._id,
});
const token = issueToken(newUser._doc);
// Sending email to user with pre defined template
const template = registerTemplate('https://www.bluewavelabs.ca');
await sendEmail([newUser.email], 'Welcome to Uptime Monitor', template, 'Registered.');
const template = registerTemplate("https://www.bluewavelabs.ca");
await sendEmail(
[newUser.email],
"Welcome to Uptime Monitor",
template,
"Registered."
);
return res
.status(200)
+1
View File
@@ -8,6 +8,7 @@ const loginValidation = joi.object({
email: joi.string().email().required(),
password: joi.string().min(8).required(),
});
const registerValidation = joi.object({
firstname: joi.string().required(),
lastname: joi.string().required(),