Skip to content

Commit

Permalink
Lesson 33 - AuthContext; Form Login;
Browse files Browse the repository at this point in the history
  • Loading branch information
fmcarvalho committed Nov 26, 2024
1 parent 1016684 commit 672feb5
Show file tree
Hide file tree
Showing 12 changed files with 4,286 additions and 0 deletions.
3,817 changes: 3,817 additions & 0 deletions lesson33-react-context-auth-and-forms/package-lock.json

Large diffs are not rendered by default.

26 changes: 26 additions & 0 deletions lesson33-react-context-auth-and-forms/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "lesson24-react-intro",
"version": "1.0.0",
"description": "",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack",
"start": "webpack serve"
},
"author": "",
"license": "ISC",
"devDependencies": {
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"ts-loader": "^9.5.1",
"typescript": "^5.6.3",
"webpack": "^5.95.0",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^5.1.0"
},
"dependencies": {
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "6.27.0"
}
}
9 changes: 9 additions & 0 deletions lesson33-react-context-auth-and-forms/public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<html>
<head>
<script src="/bundle.js" type="module"></script>
</head>
<body>
<h3>Demo React and State</h3>
<div id="container"></div>
</body>
</html>
20 changes: 20 additions & 0 deletions lesson33-react-context-auth-and-forms/src/AuthProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import * as React from 'react'

type AuthContextType = {
username: string | undefined;
setUsername: (v: string | undefined) => void
}

export const AuthContext = React.createContext<AuthContextType>({
username: undefined,
setUsername: () => { throw Error("Not implemented!") }
})

export function AuthProvider({children} : { children: React.ReactNode}) {
const [user, setUser] = React.useState(undefined)
return (
<AuthContext.Provider value={{username: user, setUsername: setUser}}>
{children}
</AuthContext.Provider>
)
}
12 changes: 12 additions & 0 deletions lesson33-react-context-auth-and-forms/src/AuthRequire.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import * as React from "react"
import { AuthContext } from "./AuthProvider"
import { Navigate, useLocation } from "react-router-dom"

export function AuthRequire({ children }: { children: React.ReactNode }) {
const { username } = React.useContext(AuthContext)
const location = useLocation()
if (username) { return <>{children}</> }
else {
return <Navigate to={"/login"} state={{source: location.pathname}}></Navigate>
}
}
106 changes: 106 additions & 0 deletions lesson33-react-context-auth-and-forms/src/Login.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import * as React from 'react';
import { Navigate, useLocation } from 'react-router-dom';
import { AuthContext } from './AuthProvider';

/***********************
* RequireAuth Component
*/
export function Login() {
const location = useLocation()
const {username, setUsername} = React.useContext(AuthContext)
const [state, dispatch] = React.useReducer(reduce, {
tag: "editing", inputs: {
username: "",
password: ""
}
})
if (state.tag === "redirect") return (
<Navigate to={location.state?.source} replace={true}></Navigate>
)
function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault()
if (state.tag != "editing") { return }
dispatch({ type: "submit" })
const { username, password } = state.inputs
authenticate(username, password)
.then(res => {
if(res) { setUsername(res) }
dispatch( res
? {type: "success"}
: {type: "error", message: `Invalid username or password: ${username} or ${password}`}
)})
.catch(err => dispatch({type: "error", message: err.message}))
}

function handleChange(event: React.ChangeEvent<HTMLInputElement>) {
dispatch({ type: "edit", inputName: event.target.name, inputValue: event.target.value })
}
const usr = state.tag === "editing" ? state.inputs.username : ""
const password = state.tag === "editing" ? state.inputs.password : ""
return (
<form onSubmit={handleSubmit}>
<fieldset disabled={state.tag !== 'editing'}>
<div>
<label htmlFor="username">Username</label>
<input id="username" type="text" name="username" value={usr} onChange={handleChange} />
</div>
<div>
<label htmlFor="password">Password</label>
<input id="password" type="text" name="password" value={password} onChange={handleChange} />
</div>
<div>
<button type="submit">Login</button>
</div>
</fieldset>
{state.tag === 'editing' && state.error}
</form>
);
}

/***********************
* REDUCER
*/

function reduce(state: State, action: Action): State {
switch (state.tag) {
case 'editing':
switch (action.type) {
case "edit": return { tag: 'editing', inputs: { ...state.inputs, [action.inputName]: action.inputValue } }
case "submit": return { tag: 'submitting' }
}
case 'submitting':
switch (action.type) {
case "success": return { tag: 'redirect' }
case "error": return { tag: 'editing', error: action.message, inputs: { username: "", password: "" } }
}
case 'redirect':
throw Error("Already in final State 'redirect' and should not reduce to any other State.")
}
}

type State = { tag: 'editing'; error?: string, inputs: { username: string, password: string }; }
| { tag: 'submitting' }
| { tag: 'redirect' }

type Action = { type: "edit", inputName: string, inputValue: string }
| { type: "submit" }
| { type: "success" }
| { type: "error", message: string }

/************************
* Auxiliary Functions emulating authenticate
*/

function delay(delayInMs: number) {
return new Promise(resolve => {
setTimeout(() => resolve(undefined), delayInMs);
});
}

async function authenticate(username: string, password: string): Promise<string | undefined> {
await delay(3000);
if ((username == 'alice' || username == 'bob') && password == '1234') {
return username;
}
return undefined;
}
26 changes: 26 additions & 0 deletions lesson33-react-context-auth-and-forms/src/ThemeProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import * as React from 'react'
import { createContext, useState } from 'react'

type Theme = "light" | "dark" | "colorful"

//Define the Context Type
type ThemeContextType = {
theme: Theme,
setTheme: (newTheme: Theme) => void
}

// Create the context
export const ThemeContext = createContext<ThemeContextType>({
theme: "light",
setTheme: () => { throw Error("Not implemented!") }
})

// Define a Provider Component
export function ThemeProvider({ children }: { children: React.ReactNode }): React.JSX.Element {
const [theme, setTheme] = useState<Theme>("light")
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
)
}
15 changes: 15 additions & 0 deletions lesson33-react-context-auth-and-forms/src/ThemeSwitcher.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import * as React from "react"

import { ThemeContext } from "./ThemeProvider"

export function ThemeSwitcher() {
const {theme, setTheme} = React.useContext(ThemeContext)
return (
<div>
<p>Current theme: {theme}</p>
<button className="button" onClick={() => setTheme("light")}>Light Mode</button>
<button className="button" onClick={() => setTheme("dark")}>Dark Mode</button>
<button className="button" onClick={() => setTheme("colorful")}>Colorful Mode</button>
</div>
)
}
Loading

0 comments on commit 672feb5

Please sign in to comment.