Skip to content

Commit

Permalink
Feature/registration (#46)
Browse files Browse the repository at this point in the history
* Create ConfirmRegistrationModal.tsx

* Create ConfirmRegistrationModal.stories.tsx

* add view BrowserViewConfirmRegistration

* Update services.tsx

* update registration modal to register effectively users
  • Loading branch information
danieleguido authored Oct 28, 2024
1 parent 86c8e20 commit acdf09e
Show file tree
Hide file tree
Showing 9 changed files with 278 additions and 55 deletions.
52 changes: 52 additions & 0 deletions src/components/ConfirmRegistrationModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Col, Container, Modal, Row } from "react-bootstrap"
import { BrowserViewConfirmRegistration } from "../constants"
import { useBrowserStore } from "../store"
import Alert from "./Alert"

const ConfirmRegistrationModal = () => {
const view = useBrowserStore((state) => state.view)
const setView = useBrowserStore((state) => state.setView)
return (
<Modal
centered
show={view === BrowserViewConfirmRegistration}
onHide={() => setView(null)}
>
<Modal.Header closeButton>
<Modal.Title>Registration almost completed...</Modal.Title>
</Modal.Header>
<Modal.Body>
<Container>
<Row>
<Col>
<p>
Thank you for completing the first step of the registration.
</p>
<Alert>Action required.</Alert>

<p className="mt-3">
Next, please download this{" "}
<a
href="https://impresso-project.ch/assets/documents/impresso_NDA.pdf"
download
>
Non-Disclosure Agreement (NDA)
</a>
, sign it and email it to{" "}
<a href="mailto:[email protected]">
[email protected]
</a>
. <br />
<br />
Once we have received the signed NDA, your account will be
activated within two working days.
</p>
</Col>
</Row>
</Container>
</Modal.Body>
</Modal>
)
}

export default ConfirmRegistrationModal
42 changes: 42 additions & 0 deletions src/components/ErrorManager.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import {
BadRequest,
NotAuthenticated,
type FeathersError,
} from "@feathersjs/errors"

type ErrorManagerProps = {
error?: FeathersError | Error | null
}
export type BadRequestData = { key?: string; message: string; label?: string }

const ErrorManager: React.FC<ErrorManagerProps> = ({ error }) => {
let errorMessages: BadRequestData[] = []

if (error instanceof BadRequest && error.data) {
errorMessages = Object.keys(error.data).map((key) => {
return {
key,
message: error.data[key].message,
label: error.data[key].label,
}
})
} else if (error instanceof NotAuthenticated) {
errorMessages = [{ key: "Error", message: error.message }]
} else if (error instanceof Error) {
errorMessages = [{ key: "Error", message: error.message }]
}
return errorMessages.length > 0 ? (
<div className="alert alert-danger" role="alert">
<ul className="list-unstyled m-0">
{errorMessages.map((d, _i) => (
<li key={_i}>
<b>{d.label ?? d.key}</b>:&nbsp;
{d.message}
</li>
))}
</ul>
</div>
) : null
}

export default ErrorManager
24 changes: 2 additions & 22 deletions src/components/LoginForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import React, { useRef } from "react"
import { Form } from "react-bootstrap"
import { useBrowserStore } from "../store"
import { BrowserViewRegister } from "../constants"
import ErrorManager from "./ErrorManager"

export interface LoginFormPayload {
email: string
Expand All @@ -33,32 +34,11 @@ const LoginForm: React.FC<LoginFormProps> = ({
}

console.info("[LoginForm] @render", { error })
let errorMessages: { key: string; message: string }[] = []

if (error instanceof BadRequest && error.data) {
errorMessages = Object.keys(error.data).map((key) => {
return { key, message: error.data[key].message }
})
} else if (error instanceof NotAuthenticated) {
errorMessages = [{ key: "Error", message: error.message }]
} else if (error) {
errorMessages = [{ key: "Error", message: error.message }]
}
return (
<>
<Form onSubmit={handleOnSubmit} className={`LoginForm ${className}`}>
{errorMessages.length > 0 ? (
<div className="alert alert-danger" role="alert">
<ul className="list-unstyled m-0">
{errorMessages.map((d, _i) => (
<li key={_i}>
<b>{d.key}</b>:&nbsp;
{d.message}
</li>
))}
</ul>
</div>
) : null}
<ErrorManager error={error} />
<Form.Group className="mb-3" controlId="ModalLoginForm.email">
<Form.Label className="font-weight-bold">Email address</Form.Label>
<Form.Control
Expand Down
2 changes: 2 additions & 0 deletions src/components/Modals.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import ConfirmRegistrationModal from "./ConfirmRegistrationModal"
import LoginModal from "./LoginModal"
import RegisterModal from "./RegisterModal"
import TermsOfUseModal from "./TermsOfUseModal"
Expand All @@ -9,6 +10,7 @@ const Modals: React.FC<{ termsOfuseContent: string }> = ({
<div className="Modals">
<LoginModal />
<RegisterModal />
<ConfirmRegistrationModal />
<TermsOfUseModal content={termsOfuseContent} autoOpenAfterDelay={false} />
</div>
)
Expand Down
134 changes: 115 additions & 19 deletions src/components/RegisterForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ import {
PlanImpressoUser,
PlanStudentUser,
PlanLabels,
BrowserViewTermsOfUse,
} from "../constants"
import { useBrowserStore, usePersistentStore } from "../store"
import { DateTime } from "luxon"
import { BadRequest, type FeathersError } from "@feathersjs/errors"
import ErrorManager, { type BadRequestData } from "./ErrorManager"

const Colors: string[] = [
"#96ceb4",
Expand Down Expand Up @@ -49,6 +54,7 @@ const Plans = [PlanImpressoUser, PlanStudentUser, PlanAcademicUser]
export interface RegisterFormPayload {
email: string
password: string
username: string
verifyPassword: string
firstname: string
lastname: string
Expand All @@ -58,11 +64,18 @@ export interface RegisterFormPayload {
export interface RegisterFormProps {
className?: string
onSubmit: (payload: RegisterFormPayload) => void
error?: FeathersError | null
}

const RegisterForm: React.FC<RegisterFormProps> = ({ className, onSubmit }) => {
const RegisterForm: React.FC<RegisterFormProps> = ({
className,
onSubmit,
error,
}) => {
const previewDelayTimerRef = useRef<NodeJS.Timeout | null>(null)

const acceptTermsDate = usePersistentStore((state) => state.acceptTermsDate)
const setView = useBrowserStore((state) => state.setView)
const [formError, setFormError] = useState<Error | null>(null)
const [formPreview, setFormPreview] = useState(() => ({
email: "",
firstname: "-",
Expand All @@ -79,14 +92,73 @@ const RegisterForm: React.FC<RegisterFormProps> = ({ className, onSubmit }) => {
email: "",
password: "",
verifyPassword: "",
username: "",
firstname: "-",
lastname: "-",
plan: PlanImpressoUser,
})
const handleOnSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
// check errors
const errorsAsData: { [key: string]: BadRequestData } = {}
console.info("[RegisterForm] @handleOnSubmit")
if (formPayload.current.password !== formPayload.current.verifyPassword) {
errorsAsData.verifyPassword = {
label: "Verify password",
message: 'Values of "password" and "verify password" do not match.',
}
}
// verufy password is complicated enough using a nice regex, numbers, uppercase andlowervase letter and a punctuation mark
if (
!formPayload.current.password.match(
/^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[a-zA-Z]).{8,}$/,
)
) {
errorsAsData.password = {
label: "Password",
message:
"Password must contain at least 8 characters, including uppercase, lowercase, numbers and a punctuation mark.",
}
}

// check email address is ok
if (!formPayload.current.email.match(/.+@.+\..+/)) {
errorsAsData.email = {
label: "Email",
message: "Please enter a valid email address.",
}
}
// check username is lwercase and number only, with '.' and '_' and "-"
if (!formPayload.current.username.match(/^[a-z0-9-]{8,}$/)) {
errorsAsData.username = {
label: "Username",
message:
'Please enter a valid username that contains at least 8 characters. We accept usernames containing only lowercase letters and numbers, e.g. "johndoe84".',
}
}
// check lastname and firstname not to be empty
if (formPayload.current.firstname.trim().length < 2) {
errorsAsData.firstname = {
label: "First name",
message: "Please enter your first name.",
}
}
if (formPayload.current.lastname.trim().length < 2) {
errorsAsData.lastname = {
label: "Last name",
message: "Please enter your last name.",
}
}
if (!formPayload.current.plan) {
errorsAsData.plan = {
label: "Plan",
message: "Please select a plan.",
}
}
if (Object.keys(errorsAsData).length > 0) {
setFormError(new BadRequest("Please check your entries.", errorsAsData))
return
}
onSubmit(formPayload.current)
}
const changeProfileColors = (e: React.MouseEvent<HTMLButtonElement>) => {
Expand All @@ -102,14 +174,6 @@ const RegisterForm: React.FC<RegisterFormProps> = ({ className, onSubmit }) => {
}))
}

const updateAgreement = (agreedToTerms: boolean) => {
setFormPreview((state) => ({
...state,
agreedToTerms,
}))
console.info("[RegisterForm] @updateAgreement", agreedToTerms)
}

const updatePreview = (key: keyof RegisterFormPayload, value: string) => {
formPayload.current[key] = value
console.info("[RegisterForm] @updatePreview", key, value)
Expand Down Expand Up @@ -141,27 +205,43 @@ const RegisterForm: React.FC<RegisterFormProps> = ({ className, onSubmit }) => {

return (
<Form onSubmit={handleOnSubmit} className={`RegisterForm ${className}`}>
<ErrorManager error={formError || error} />
<section className="mb-3 d-flex flex-wrap gap-2 align-items-center">
{Plans.map((plan) => (
<Form.Check
className="border rounded"
key={plan}
type="radio"
label={PlanLabels[plan]}
checked={formPayload.current.plan === plan}
name="plan"
onChange={() => updatePreview("plan", plan)}
id={`ModalRegisterForm.${plan}`}
/>
))}
</section>
<Form.Group className="mb-3" controlId="ModalRegisterForm.email">
<Form.Label className="font-weight-bold">Email address</Form.Label>
<Form.Control
onChange={(e) => updatePreview("email", e.target.value)}
type="email"
placeholder="[email protected]"
/>
</Form.Group>
<Row>
<Col>
<Form.Group className="mb-3" controlId="ModalRegisterForm.email">
<Form.Label className="font-weight-bold">Email address</Form.Label>
<Form.Control
onChange={(e) => updatePreview("email", e.target.value)}
type="email"
placeholder="[email protected]"
/>
</Form.Group>
</Col>
<Col>
{/* username */}
<Form.Group className="mb-3" controlId="ModalRegisterForm.username">
<Form.Label className="font-weight-bold">Username</Form.Label>
<Form.Control
onChange={(e) => updatePreview("username", e.target.value)}
placeholder="your username"
/>
</Form.Group>
</Col>
</Row>
<Form.Group className="mb-3" controlId="ModalRegisterForm.firstname">
<Form.Label className="font-weight-bold">First name</Form.Label>
<Form.Control
Expand Down Expand Up @@ -206,9 +286,25 @@ const RegisterForm: React.FC<RegisterFormProps> = ({ className, onSubmit }) => {
</Row>
<Form.Group className="mb-3" controlId="ModalRegisterForm.agreedToTerms">
<Form.Check
onChange={(e) => updateAgreement(e.target.checked)}
checked={acceptTermsDate !== null}
onChange={(e) => {
if (acceptTermsDate) {
return
}
setView(BrowserViewTermsOfUse)
}}
label="I agree to the terms and conditions"
/>
{acceptTermsDate !== null && (
<p className="m-2 px-3">
You accepted the Terms of Use <br />
<b>
{DateTime.fromISO(acceptTermsDate)
.setLocale("en-GB")
.toLocaleString(DateTime.DATETIME_FULL)}
</b>
</p>
)}
</Form.Group>
<section className="d-flex gap-4 align-items-center mb-2">
<div>Preview:</div>
Expand Down
Loading

0 comments on commit acdf09e

Please sign in to comment.