implement basic login
This commit is contained in:
parent
bdd90b50d9
commit
7a6f5b5bcd
18 changed files with 222 additions and 35 deletions
|
|
@ -2,9 +2,10 @@ import { BrowserRouter as Router, Routes, Route } from "react-router-dom"
|
|||
import RecipeDetailPage from "./components/recipes/RecipeDetailPage"
|
||||
import RecipeEditPage from "./components/recipes/RecipeEditPage"
|
||||
import RecipeListPage from "./components/recipes/RecipeListPage"
|
||||
import { getRecipeAddUrlDefinition, getRecipeDetailsUrlDefinition, getRecipeEditUrlDefinition, getRootUrlDefinition } from "./routes"
|
||||
import { getLoginUrl, getRecipeAddUrlDefinition, getRecipeDetailsUrlDefinition, getRecipeEditUrlDefinition, getRecipeListUrlDefinition, getRootUrlDefinition } from "./routes"
|
||||
|
||||
import "./App.css"
|
||||
import LoginPage from "./components/LoginPage"
|
||||
|
||||
/**
|
||||
* Main application component.
|
||||
|
|
@ -14,8 +15,10 @@ function App() {
|
|||
return (
|
||||
<Router>
|
||||
<Routes>
|
||||
{/* Login page */}
|
||||
<Route path={getLoginUrl()} element={<LoginPage/>}/>
|
||||
{/* Home page: list of recipes */}
|
||||
<Route path= {getRootUrlDefinition()} element={<RecipeListPage />} />
|
||||
<Route path= {getRecipeListUrlDefinition()} element={<RecipeListPage />} />
|
||||
|
||||
{/* Detail page: shows one recipe */}
|
||||
<Route path={getRecipeDetailsUrlDefinition()} element={<RecipeDetailPage />} />
|
||||
|
|
|
|||
7
frontend/src/api/dtos/LoginRequestDto.ts
Normal file
7
frontend/src/api/dtos/LoginRequestDto.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
/**
|
||||
* Defines a login request
|
||||
*/
|
||||
export class LoginRequestDto {
|
||||
userName?: string;
|
||||
password?: string;
|
||||
}
|
||||
10
frontend/src/api/dtos/LoginResponseDto.ts
Normal file
10
frontend/src/api/dtos/LoginResponseDto.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { UserDto } from "./UserDto.js";
|
||||
|
||||
/**
|
||||
* Response to a successful login
|
||||
*/
|
||||
export class LoginResponseDto {
|
||||
userData?: UserDto;
|
||||
token?: string;
|
||||
expiryDate? : Date;
|
||||
}
|
||||
15
frontend/src/api/dtos/RecipeDto.ts
Normal file
15
frontend/src/api/dtos/RecipeDto.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
|
||||
import { AbstractDto } from "./AbstractDto.js";
|
||||
import { RecipeIngredientGroupDto } from "./RecipeIngredientGroupDto.js";
|
||||
import { RecipeInstructionStepDto } from "./RecipeInstructionStepDto.js";
|
||||
/**
|
||||
* DTO describing a recipe
|
||||
*/
|
||||
|
||||
export class RecipeDto extends AbstractDto {
|
||||
title!: string;
|
||||
amount?: number
|
||||
amountDescription?: string;
|
||||
instructions!: RecipeInstructionStepDto[];
|
||||
ingredientGroups!: RecipeIngredientGroupDto[];
|
||||
}
|
||||
10
frontend/src/api/dtos/RecipeIngredientDto.ts
Normal file
10
frontend/src/api/dtos/RecipeIngredientDto.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { AbstractDto } from "./AbstractDto.js";
|
||||
|
||||
export class RecipeIngredientDto extends AbstractDto{
|
||||
name!: string;
|
||||
subtext?: string;
|
||||
amount?: number;
|
||||
unit?: string;
|
||||
sortOrder!: number;
|
||||
ingredientGroupId?: string;
|
||||
}
|
||||
9
frontend/src/api/dtos/RecipeIngredientGroupDto.ts
Normal file
9
frontend/src/api/dtos/RecipeIngredientGroupDto.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import { AbstractDto } from "./AbstractDto.js";
|
||||
import { RecipeIngredientDto } from "./RecipeIngredientDto.js";
|
||||
|
||||
export class RecipeIngredientGroupDto extends AbstractDto{
|
||||
title?: string;
|
||||
sortOrder!: number;
|
||||
recipeId?: string;
|
||||
ingredients!: RecipeIngredientDto[];
|
||||
}
|
||||
8
frontend/src/api/dtos/RecipeInstructionStepDto.ts
Normal file
8
frontend/src/api/dtos/RecipeInstructionStepDto.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { UUID } from "crypto";
|
||||
import { AbstractDto } from "./AbstractDto.js";
|
||||
|
||||
export class RecipeInstructionStepDto extends AbstractDto{
|
||||
text!: string;
|
||||
sortOrder!: number;
|
||||
recipeId?: UUID;
|
||||
}
|
||||
9
frontend/src/api/dtos/UserDto.ts
Normal file
9
frontend/src/api/dtos/UserDto.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import { AbstractDto } from "./AbstractDto.js";
|
||||
|
||||
export class UserDto extends AbstractDto {
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
userName!: string;
|
||||
email!: string;
|
||||
role?: string;
|
||||
}
|
||||
27
frontend/src/api/points/AuthPoint.ts
Normal file
27
frontend/src/api/points/AuthPoint.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import type { Recipe } from "../../types/recipe"
|
||||
import type { LoginRequestDto } from "../dtos/LoginRequestDto";
|
||||
import type { LoginResponseDto } from "../dtos/LoginResponseDto";
|
||||
import { get, postJson, putJson } from "../utils/requests";
|
||||
|
||||
|
||||
/**
|
||||
* Util for handling the recipe api
|
||||
*/
|
||||
// read base url from .env file
|
||||
const BASE_URL = import.meta.env.VITE_API_BASE;
|
||||
|
||||
/**
|
||||
* URL for handling recipes
|
||||
*/
|
||||
const AUTH_URL = `${BASE_URL}/auth`
|
||||
|
||||
|
||||
/**
|
||||
* Create new Recipe
|
||||
* @param recipe Recipe to create
|
||||
* @returns Saved recipe
|
||||
*/
|
||||
export async function login(requestDto: LoginRequestDto): Promise<LoginResponseDto> {
|
||||
const res = await postJson(`${AUTH_URL}/login`, JSON.stringify(requestDto), false);
|
||||
return res.json();
|
||||
}
|
||||
29
frontend/src/api/points/CompactRecipePoint.ts
Normal file
29
frontend/src/api/points/CompactRecipePoint.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import type { Recipe } from "../../types/recipe"
|
||||
import { get, postJson, putJson } from "../utils/requests";
|
||||
|
||||
|
||||
/**
|
||||
* Util for handling the recipe api
|
||||
*/
|
||||
// read base url from .env file
|
||||
const BASE_URL = import.meta.env.VITE_API_BASE;
|
||||
|
||||
/**
|
||||
* URL for handling recipes header data
|
||||
*/
|
||||
const RECIPE_URL = `${BASE_URL}/compact-recipe`
|
||||
|
||||
/**
|
||||
* Load list of all recipes
|
||||
* @param searchString Search string for filtering recipeList
|
||||
* @returns Array of recipe
|
||||
*/
|
||||
export async function fetchRecipeList(searchString : string): Promise<Recipe[]> {
|
||||
let url : string = RECIPE_URL;
|
||||
// if there's a search string add it as query parameter
|
||||
if(searchString && searchString !== ""){
|
||||
url +="?search=" + searchString;
|
||||
}
|
||||
const res = await get(url);
|
||||
return res.json();
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import type { Recipe } from "../types/recipe"
|
||||
import { get, postJson, putJson } from "./utils/requests";
|
||||
import type { Recipe } from "../../types/recipe"
|
||||
import { get, postJson, putJson } from "../utils/requests";
|
||||
|
||||
|
||||
/**
|
||||
|
|
@ -23,21 +23,6 @@ export async function fetchRecipe(id: string): Promise<Recipe> {
|
|||
return res.json()
|
||||
}
|
||||
|
||||
/**
|
||||
* Load list of all recipes
|
||||
* @param searchString Search string for filtering recipeList
|
||||
* @returns Array of recipe
|
||||
*/
|
||||
export async function fetchRecipeList(searchString : string): Promise<Recipe[]> {
|
||||
let url : string = RECIPE_URL;
|
||||
// if there's a search string add it as query parameter
|
||||
if(searchString && searchString !== ""){
|
||||
url +="?search=" + searchString;
|
||||
}
|
||||
const res = await get(url);
|
||||
return res.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new Recipe
|
||||
* @param recipe Recipe to create
|
||||
|
|
@ -1,3 +1,12 @@
|
|||
|
||||
const BASE_URL = import.meta.env.VITE_API_BASE;
|
||||
|
||||
export function createBasicHeader() : Headers {
|
||||
const headers = new Headers();
|
||||
//headers.set('Access-Control-Allow-Origin', '*');
|
||||
return headers;
|
||||
}
|
||||
|
||||
export function setContentTypeHeaderJson(headers: Headers){
|
||||
return headers.set("Content-Type", "application/json");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { setAuthHeader, setContentTypeHeaderJson } from "./headers";
|
||||
import { createBasicHeader, setAuthHeader, setContentTypeHeaderJson } from "./headers";
|
||||
|
||||
export async function get(url: string) : Promise<Response>{
|
||||
const requestHeaders = new Headers();
|
||||
const requestHeaders = createBasicHeader();
|
||||
setAuthHeader(requestHeaders);
|
||||
console.log("GET to " + url);
|
||||
const response = await fetch(url, {
|
||||
|
|
@ -15,26 +15,35 @@ export async function get(url: string) : Promise<Response>{
|
|||
}
|
||||
|
||||
export async function postJson(url: string, requestBody: string, logBody = true) : Promise<Response>{
|
||||
console.log("POST to " + url + (logBody) ? "with body " + requestBody : "");
|
||||
return persistJson(url, requestBody, "POST");
|
||||
console.log("POST to " + url);
|
||||
if(logBody){
|
||||
console.log("body: " + requestBody);
|
||||
}
|
||||
return await persistJson(url, requestBody, "POST");
|
||||
}
|
||||
|
||||
export async function putJson(url: string, requestBody: string, logBody = true) : Promise<Response>{
|
||||
console.log("PUT to " + url + (logBody) ? "with body " + requestBody : "");
|
||||
console.log("PUT to " + url);
|
||||
if(logBody){
|
||||
console.log("body: " + requestBody);
|
||||
}
|
||||
return persistJson(url, requestBody, "PUT");
|
||||
}
|
||||
|
||||
async function persistJson(url: string, requestBody: string, requestMethod: string) : Promise<Response>{
|
||||
const requestHeaders = new Headers();
|
||||
const requestHeaders = createBasicHeader();
|
||||
setContentTypeHeaderJson(requestHeaders);
|
||||
setAuthHeader(requestHeaders);
|
||||
const response = await fetch(url, {
|
||||
const request = new Request(url, {
|
||||
method: requestMethod,
|
||||
headers: requestHeaders,
|
||||
body: requestBody,
|
||||
});
|
||||
console.log(request)
|
||||
const response = await fetch(request);
|
||||
console.log("response.ok", response.ok)
|
||||
if(!response.ok){
|
||||
throw new Error(requestMethod + " to " + url + "failed!")
|
||||
throw new Error(requestMethod + " to " + url + " failed!")
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
|
|
|||
55
frontend/src/components/LoginPage.tsx
Normal file
55
frontend/src/components/LoginPage.tsx
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
import { useState } from "react";
|
||||
import Button from "./basics/Button";
|
||||
import type { LoginRequestDto } from "../api/dtos/LoginRequestDto";
|
||||
import type { LoginResponseDto } from "../api/dtos/LoginResponseDto";
|
||||
import { login } from "../api/points/AuthPoint";
|
||||
import { getRecipeListUrl } from "../routes";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
export default function LoginPage(){
|
||||
const [userName, setUserName] = useState<string>("");
|
||||
const [password, setPassword] = useState<string>("");
|
||||
const navigate = useNavigate();
|
||||
const executeLogin = async () =>{
|
||||
const dto : LoginRequestDto = {
|
||||
userName: userName,
|
||||
password: password
|
||||
}
|
||||
try{
|
||||
// @todo move to auth handler
|
||||
console.log("trying to log in with " + dto.userName)
|
||||
const loginResponse : LoginResponseDto = await login(dto);
|
||||
localStorage.setItem("session", JSON.stringify(loginResponse));
|
||||
console.log("Successfully logged on with user " + loginResponse.userData?.userName)
|
||||
navigate(getRecipeListUrl());
|
||||
} catch(err){
|
||||
// @todo show error in GUI
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
return(
|
||||
<div className="p-6 max-w-2xl mx-auto">
|
||||
<input
|
||||
className="input-field"
|
||||
placeholder="Benutzername"
|
||||
value = {userName}
|
||||
onChange={e => {
|
||||
setUserName(e.target.value)
|
||||
}}
|
||||
/>
|
||||
{/* @todo Password mode!!! */}
|
||||
<input
|
||||
className="input-field"
|
||||
placeholder="Passwort"
|
||||
value = {password}
|
||||
onChange={e => {
|
||||
setPassword(e.target.value)
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
text="Login"
|
||||
onClick= {executeLogin}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { useParams, Link } from "react-router-dom"
|
||||
import { useParams } from "react-router-dom"
|
||||
import type { Recipe } from "../../types/recipe"
|
||||
import { useEffect, useState } from "react"
|
||||
import { fetchRecipe } from "../../api/recipePoint"
|
||||
import { fetchRecipe } from "../../api/points/RecipePoint"
|
||||
import { getRecipeEditUrl, getRecipeListUrl } from "../../routes"
|
||||
import ButtonLink from "../basics/ButtonLink"
|
||||
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ import { useParams, useNavigate } from "react-router-dom"
|
|||
import { useEffect, useState } from "react"
|
||||
import type { Recipe } from "../../types/recipe"
|
||||
import RecipeEditor from "./RecipeEditor"
|
||||
import { fetchRecipe, createRecipe, updateRecipe } from "../../api/recipePoint"
|
||||
import { getRecipeDetailUrl, getRecipeListUrl, getRootUrl } from "../../routes"
|
||||
import { fetchRecipe, createRecipe, updateRecipe } from "../../api/points/RecipePoint"
|
||||
import { getRecipeDetailUrl, getRecipeListUrl } from "../../routes"
|
||||
|
||||
export default function RecipeEditPage() {
|
||||
// Extract recipe ID from route params
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
import { useEffect, useState } from "react"
|
||||
import RecipeListItem from "./RecipeListItem"
|
||||
import type { Recipe } from "../../types/recipe"
|
||||
import { fetchRecipeList } from "../../api/recipePoint"
|
||||
import { fetchRecipeList } from "../../api/points/CompactRecipePoint"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import { getRecipeAddUrl, getRecipeDetailUrl } from "../../routes"
|
||||
import SearchField from "../basics/SearchField"
|
||||
import RecipeListToolbar from "./RecipeListToolbar"
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -6,10 +6,13 @@ export function getRootUrlDefinition() : string { return getRootUrl()}
|
|||
export function getRecipeDetailsUrlDefinition() : string {return getRecipeDetailUrl(":id")}
|
||||
export function getRecipeEditUrlDefinition() : string {return getRecipeEditUrl(":id")}
|
||||
export function getRecipeAddUrlDefinition() : string {return getRecipeAddUrl()}
|
||||
export function getRecipeListUrlDefinition() : string {return getRecipeListUrl()}
|
||||
export function getLoginUrlDefinition() : string {return getLoginUrl()}
|
||||
|
||||
// URLs including id
|
||||
export function getRootUrl () : string { return "/"}
|
||||
export function getRecipeListUrl() : string {return getRootUrl()}
|
||||
export function getRecipeListUrl() : string {return "/recipe/list"}
|
||||
export function getRecipeDetailUrl(id: string) : string {return "/recipe/" + id + "/card"}
|
||||
export function getRecipeEditUrl(id: string) : string {return "/recipe/" + id + "/edit"}
|
||||
export function getRecipeAddUrl() : string {return "/recipe/add"}
|
||||
export function getLoginUrl() : string {return "/login"}
|
||||
Loading…
Add table
Add a link
Reference in a new issue