-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Create ConfirmRegistrationModal.tsx * Create ConfirmRegistrationModal.stories.tsx * add view BrowserViewConfirmRegistration * Update services.tsx * update registration modal to register effectively users
- Loading branch information
1 parent
86c8e20
commit acdf09e
Showing
9 changed files
with
278 additions
and
55 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
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 |
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,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>: | ||
{d.message} | ||
</li> | ||
))} | ||
</ul> | ||
</div> | ||
) : null | ||
} | ||
|
||
export default ErrorManager |
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
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 |
---|---|---|
|
@@ -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", | ||
|
@@ -49,6 +54,7 @@ const Plans = [PlanImpressoUser, PlanStudentUser, PlanAcademicUser] | |
export interface RegisterFormPayload { | ||
email: string | ||
password: string | ||
username: string | ||
verifyPassword: string | ||
firstname: string | ||
lastname: string | ||
|
@@ -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: "-", | ||
|
@@ -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>) => { | ||
|
@@ -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) | ||
|
@@ -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 | ||
|
@@ -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> | ||
|
Oops, something went wrong.