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 RecipeDetailPage from "./components/recipes/RecipeDetailPage"
|
||||||
import RecipeEditPage from "./components/recipes/RecipeEditPage"
|
import RecipeEditPage from "./components/recipes/RecipeEditPage"
|
||||||
import RecipeListPage from "./components/recipes/RecipeListPage"
|
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 "./App.css"
|
||||||
|
import LoginPage from "./components/LoginPage"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main application component.
|
* Main application component.
|
||||||
|
|
@ -14,8 +15,10 @@ function App() {
|
||||||
return (
|
return (
|
||||||
<Router>
|
<Router>
|
||||||
<Routes>
|
<Routes>
|
||||||
|
{/* Login page */}
|
||||||
|
<Route path={getLoginUrl()} element={<LoginPage/>}/>
|
||||||
{/* Home page: list of recipes */}
|
{/* Home page: list of recipes */}
|
||||||
<Route path= {getRootUrlDefinition()} element={<RecipeListPage />} />
|
<Route path= {getRecipeListUrlDefinition()} element={<RecipeListPage />} />
|
||||||
|
|
||||||
{/* Detail page: shows one recipe */}
|
{/* Detail page: shows one recipe */}
|
||||||
<Route path={getRecipeDetailsUrlDefinition()} element={<RecipeDetailPage />} />
|
<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 type { Recipe } from "../../types/recipe"
|
||||||
import { get, postJson, putJson } from "./utils/requests";
|
import { get, postJson, putJson } from "../utils/requests";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -23,21 +23,6 @@ export async function fetchRecipe(id: string): Promise<Recipe> {
|
||||||
return res.json()
|
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
|
* Create new Recipe
|
||||||
* @param recipe Recipe to create
|
* @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){
|
export function setContentTypeHeaderJson(headers: Headers){
|
||||||
return headers.set("Content-Type", "application/json");
|
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>{
|
export async function get(url: string) : Promise<Response>{
|
||||||
const requestHeaders = new Headers();
|
const requestHeaders = createBasicHeader();
|
||||||
setAuthHeader(requestHeaders);
|
setAuthHeader(requestHeaders);
|
||||||
console.log("GET to " + url);
|
console.log("GET to " + url);
|
||||||
const response = await fetch(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>{
|
export async function postJson(url: string, requestBody: string, logBody = true) : Promise<Response>{
|
||||||
console.log("POST to " + url + (logBody) ? "with body " + requestBody : "");
|
console.log("POST to " + url);
|
||||||
return persistJson(url, requestBody, "POST");
|
if(logBody){
|
||||||
|
console.log("body: " + requestBody);
|
||||||
|
}
|
||||||
|
return await persistJson(url, requestBody, "POST");
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function putJson(url: string, requestBody: string, logBody = true) : Promise<Response>{
|
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");
|
return persistJson(url, requestBody, "PUT");
|
||||||
}
|
}
|
||||||
|
|
||||||
async function persistJson(url: string, requestBody: string, requestMethod: string) : Promise<Response>{
|
async function persistJson(url: string, requestBody: string, requestMethod: string) : Promise<Response>{
|
||||||
const requestHeaders = new Headers();
|
const requestHeaders = createBasicHeader();
|
||||||
setContentTypeHeaderJson(requestHeaders);
|
setContentTypeHeaderJson(requestHeaders);
|
||||||
setAuthHeader(requestHeaders);
|
setAuthHeader(requestHeaders);
|
||||||
const response = await fetch(url, {
|
const request = new Request(url, {
|
||||||
method: requestMethod,
|
method: requestMethod,
|
||||||
headers: requestHeaders,
|
headers: requestHeaders,
|
||||||
body: requestBody,
|
body: requestBody,
|
||||||
});
|
});
|
||||||
|
console.log(request)
|
||||||
|
const response = await fetch(request);
|
||||||
|
console.log("response.ok", response.ok)
|
||||||
if(!response.ok){
|
if(!response.ok){
|
||||||
throw new Error(requestMethod + " to " + url + "failed!")
|
throw new Error(requestMethod + " to " + url + " failed!")
|
||||||
}
|
}
|
||||||
return response;
|
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 type { Recipe } from "../../types/recipe"
|
||||||
import { useEffect, useState } from "react"
|
import { useEffect, useState } from "react"
|
||||||
import { fetchRecipe } from "../../api/recipePoint"
|
import { fetchRecipe } from "../../api/points/RecipePoint"
|
||||||
import { getRecipeEditUrl, getRecipeListUrl } from "../../routes"
|
import { getRecipeEditUrl, getRecipeListUrl } from "../../routes"
|
||||||
import ButtonLink from "../basics/ButtonLink"
|
import ButtonLink from "../basics/ButtonLink"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ import { useParams, useNavigate } from "react-router-dom"
|
||||||
import { useEffect, useState } from "react"
|
import { useEffect, useState } from "react"
|
||||||
import type { Recipe } from "../../types/recipe"
|
import type { Recipe } from "../../types/recipe"
|
||||||
import RecipeEditor from "./RecipeEditor"
|
import RecipeEditor from "./RecipeEditor"
|
||||||
import { fetchRecipe, createRecipe, updateRecipe } from "../../api/recipePoint"
|
import { fetchRecipe, createRecipe, updateRecipe } from "../../api/points/RecipePoint"
|
||||||
import { getRecipeDetailUrl, getRecipeListUrl, getRootUrl } from "../../routes"
|
import { getRecipeDetailUrl, getRecipeListUrl } from "../../routes"
|
||||||
|
|
||||||
export default function RecipeEditPage() {
|
export default function RecipeEditPage() {
|
||||||
// Extract recipe ID from route params
|
// Extract recipe ID from route params
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
import { useEffect, useState } from "react"
|
import { useEffect, useState } from "react"
|
||||||
import RecipeListItem from "./RecipeListItem"
|
import RecipeListItem from "./RecipeListItem"
|
||||||
import type { Recipe } from "../../types/recipe"
|
import type { Recipe } from "../../types/recipe"
|
||||||
import { fetchRecipeList } from "../../api/recipePoint"
|
import { fetchRecipeList } from "../../api/points/CompactRecipePoint"
|
||||||
import { useNavigate } from "react-router-dom"
|
import { useNavigate } from "react-router-dom"
|
||||||
import { getRecipeAddUrl, getRecipeDetailUrl } from "../../routes"
|
import { getRecipeAddUrl, getRecipeDetailUrl } from "../../routes"
|
||||||
import SearchField from "../basics/SearchField"
|
|
||||||
import RecipeListToolbar from "./RecipeListToolbar"
|
import RecipeListToolbar from "./RecipeListToolbar"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,13 @@ export function getRootUrlDefinition() : string { return getRootUrl()}
|
||||||
export function getRecipeDetailsUrlDefinition() : string {return getRecipeDetailUrl(":id")}
|
export function getRecipeDetailsUrlDefinition() : string {return getRecipeDetailUrl(":id")}
|
||||||
export function getRecipeEditUrlDefinition() : string {return getRecipeEditUrl(":id")}
|
export function getRecipeEditUrlDefinition() : string {return getRecipeEditUrl(":id")}
|
||||||
export function getRecipeAddUrlDefinition() : string {return getRecipeAddUrl()}
|
export function getRecipeAddUrlDefinition() : string {return getRecipeAddUrl()}
|
||||||
|
export function getRecipeListUrlDefinition() : string {return getRecipeListUrl()}
|
||||||
|
export function getLoginUrlDefinition() : string {return getLoginUrl()}
|
||||||
|
|
||||||
// URLs including id
|
// URLs including id
|
||||||
export function getRootUrl () : string { return "/"}
|
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 getRecipeDetailUrl(id: string) : string {return "/recipe/" + id + "/card"}
|
||||||
export function getRecipeEditUrl(id: string) : string {return "/recipe/" + id + "/edit"}
|
export function getRecipeEditUrl(id: string) : string {return "/recipe/" + id + "/edit"}
|
||||||
export function getRecipeAddUrl() : string {return "/recipe/add"}
|
export function getRecipeAddUrl() : string {return "/recipe/add"}
|
||||||
|
export function getLoginUrl() : string {return "/login"}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue