Skip to content

Commit

Permalink
Fix/issue 33 login form (#36)
Browse files Browse the repository at this point in the history
* add error type from Feathers

* Update LoginForm.tsx

* add error from API in LoginModal

* Update entities.mdx

* improve secondary button style

* fix image layout in markdown

* fixupdatenotebooks to update only notebooks without the SHA or with a different SHA

---------

Co-authored-by: Daniele Guido <1181642+danieleguido@users.noreply.github.com>
  • Loading branch information
danieleguido and danieleguido authored Oct 18, 2024
1 parent 38407d3 commit 06da915
Show file tree
Hide file tree
Showing 10 changed files with 170 additions and 118 deletions.
14 changes: 8 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"@codemirror/lang-python": "^6.1.6",
"@custom-react-hooks/use-on-screen": "^1.5.1",
"@feathersjs/authentication-client": "^5.0.30",
"@feathersjs/errors": "^5.0.30",
"@feathersjs/feathers": "^5.0.30",
"@feathersjs/socketio-client": "^5.0.30",
"@react-spring/web": "^9.7.4",
Expand Down
108 changes: 87 additions & 21 deletions src/components/LoginForm.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import {
BadRequest,
NotAuthenticated,
type FeathersError,
} from "@feathersjs/errors"
import React, { useRef } from "react"
import { Form } from "react-bootstrap"
import { useBrowserStore } from "../store"
import { BrowserViewRegister } from "../constants"

export interface LoginFormPayload {
email: string
Expand All @@ -9,37 +16,96 @@ export interface LoginFormPayload {
export interface LoginFormProps {
className?: string
onSubmit: (payload: LoginFormPayload) => void
error?: FeathersError | null
}

const LoginForm: React.FC<LoginFormProps> = ({ className, onSubmit }) => {
const LoginForm: React.FC<LoginFormProps> = ({
className,
onSubmit,
error,
}) => {
const setView = useBrowserStore((state) => state.setView)
const formPayload = useRef({ email: "", password: "" })
const handleOnSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
console.info("[LoginForm] @handleOnSubmit")
onSubmit(formPayload.current)
}

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}`}>
<Form.Group className="mb-3" controlId="ModalLoginForm.email">
<Form.Label className="font-weight-bold">Email address</Form.Label>
<Form.Control
onChange={(e) => (formPayload.current.email = e.target.value)}
type="email"
placeholder="name@example.com"
/>
</Form.Group>
<Form.Group className="mb-3" controlId="ModalLoginForm.password">
<Form.Label className="font-weight-bold">Password</Form.Label>
<Form.Control
onChange={(e) => (formPayload.current.password = e.target.value)}
type="password"
/>
</Form.Group>
<button type="submit" className="btn btn-primary">
Log in
</button>
</Form>
<>
<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}
<Form.Group className="mb-3" controlId="ModalLoginForm.email">
<Form.Label className="font-weight-bold">Email address</Form.Label>
<Form.Control
onChange={(e) => (formPayload.current.email = e.target.value)}
type="email"
placeholder="name@example.com"
/>
</Form.Group>
<Form.Group className="mb-3" controlId="ModalLoginForm.password">
<Form.Label className="font-weight-bold">Password</Form.Label>
<Form.Control
onChange={(e) => (formPayload.current.password = e.target.value)}
type="password"
/>
</Form.Group>
<div className="">
<button
type="submit"
className="btn btn-primary w-100 d-flex justify-content-center px-5"
>
Log in
</button>
</div>
</Form>
{/* Did you forget your password? */}
<p className="mt-3">
Did you forget your password?{" "}
<a href="https://impresso-project.ch/app/password-reset">
Reset your password
</a>
</p>
<p className="my-3 py-3 gap-3 border-top border-bottom d-flex align-items-center">
Don't have an account?
<button
onClick={() => setView(BrowserViewRegister)}
className="btn btn-secondary px-5"
>
Register
</button>
</p>

<p className="mt-2">
Any Questions? <br />
Contact us at{" "}
<a href="mailto:info@impresso-project.ch">info@impresso-project.ch</a>
</p>
</>
)
}

Expand Down
13 changes: 10 additions & 3 deletions src/components/LoginModal.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import { Modal } from "react-bootstrap"
import { FeathersError } from "@feathersjs/errors"
import { useBrowserStore, usePersistentStore } from "../store"
import { BrowserViewLogin } from "../constants"
import LoginForm, { type LoginFormPayload } from "./LoginForm"
import { loginService } from "../services"
import { useEffect, useState } from "react"

const LoginModal = () => {
const view = useBrowserStore((state) => state.view)
const setView = useBrowserStore((state) => state.setView)
const setAuthenticatedUser = usePersistentStore(
(state) => state.setAuthenticatedUser
(state) => state.setAuthenticatedUser,
)
const [error, setError] = useState<FeathersError | null>(null)

const checkCredentials = (credentials: LoginFormPayload) => {
loginService
Expand All @@ -22,11 +25,15 @@ const LoginModal = () => {
setAuthenticatedUser(data.user, data.accessToken)
setView(null)
})
.catch((err) => {
.catch((err: FeathersError) => {
setError(err)
console.error("loginService.create", err)
})
}

useEffect(() => {
setError(null)
}, [view])
return (
<Modal
centered
Expand All @@ -38,7 +45,7 @@ const LoginModal = () => {
</Modal.Header>
<Modal.Body className="p-3">
<p>Log in to your account</p>
<LoginForm onSubmit={checkCredentials} />
<LoginForm onSubmit={checkCredentials} error={error} />
</Modal.Body>
</Modal>
)
Expand Down
6 changes: 6 additions & 0 deletions src/components/MarkdownSnippet.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.MarkdownSnippet img {
max-width: 100%;
height: auto;
display: block;
margin: 0 auto;
}
3 changes: 2 additions & 1 deletion src/components/MarkdownSnippet.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { marked } from "marked"
import "./MarkdownSnippet.css"

export interface MarkdownSnippetProps {
value?: string
Expand All @@ -12,7 +13,7 @@ const MarkdownSnippet: React.FC<MarkdownSnippetProps> = ({
const content = marked.parse(value)
return (
<div
className={`MarkdownSnipped ${className}`}
className={`MarkdownSnippet ${className}`}
dangerouslySetInnerHTML={{ __html: content }}
/>
)
Expand Down
78 changes: 18 additions & 60 deletions src/content/notebooks/impresso-py-connect.mdx

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion src/content/series/entities.mdx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
---
title: Explore and Visualise your Impresso Data
excerpt: "Notebook templates offer complementary views on your Impresso personal collections and external datasets beyond the capabilities of the Impresso Web App."
excerpt: ""
notebooks:
- impresso-py-maps
- impresso-py-network
category:
- explorations
position: central-column
---

Notebook templates offer complementary views on your Impresso personal collections and external datasets beyond the capabilities of the Impresso Web App.
Loading

0 comments on commit 06da915

Please sign in to comment.