tuned header of recipe lists for smartphones

This commit is contained in:
Anika Raemer 2025-09-14 17:30:33 +02:00
parent 5c3c74b32e
commit 686eddbaee
24 changed files with 2563 additions and 29 deletions

3
frontend/.gitignore vendored
View file

@ -12,6 +12,9 @@ dist
dist-ssr
*.local
.env
.env.*
# Editor directories and files
.vscode/*
!.vscode/extensions.json

View file

@ -15,6 +15,7 @@
"devDependencies": {
"@eslint/js": "^9.33.0",
"@tailwindcss/postcss": "^4.1.13",
"@types/node": "^24.4.0",
"@types/react": "^19.1.10",
"@types/react-dom": "^19.1.7",
"@types/react-router-dom": "^5.3.3",
@ -1718,6 +1719,16 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/node": {
"version": "24.4.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.4.0.tgz",
"integrity": "sha512-gUuVEAK4/u6F9wRLznPUU4WGUacSEBDPoC2TrBkw3GAnOLHBL45QdfHOXp1kJ4ypBGLxTOB+t7NJLpKoC3gznQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~7.11.0"
}
},
"node_modules/@types/react": {
"version": "19.1.12",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.12.tgz",
@ -4030,6 +4041,13 @@
"typescript": ">=4.8.4 <6.0.0"
}
},
"node_modules/undici-types": {
"version": "7.11.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.11.0.tgz",
"integrity": "sha512-kt1ZriHTi7MU+Z/r9DOdAI3ONdaR3M3csEaRc6ewa4f4dTvX4cQCbJ4NkEn0ohE4hHtq85+PhPSTY+pO/1PwgA==",
"dev": true,
"license": "MIT"
},
"node_modules/update-browserslist-db": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",

View file

@ -17,6 +17,7 @@
"devDependencies": {
"@eslint/js": "^9.33.0",
"@tailwindcss/postcss": "^4.1.13",
"@types/node": "^24.4.0",
"@types/react": "^19.1.10",
"@types/react-dom": "^19.1.7",
"@types/react-router-dom": "^5.3.3",

View file

@ -1,13 +1,16 @@
import type { Recipe } from "../types/recipe"
import { API_BASE_URL } from "../config/api"
/**
* Util for handling the recipe api
*/
// reate base url from .env file
const BASE_URL = import.meta.env.VITE_API_BASE;
/**
* URL for handling recipes
*/
const RECIPE_URL = `${API_BASE_URL}/recipe`
const RECIPE_URL = `${BASE_URL}/recipe`
/**
* Load a single recipe

View file

@ -35,7 +35,7 @@ export default function RecipeListPage() {
navigate(getRecipeAddUrl())
}
if(!recipeList) { return <div>Unable to load recipes!</div>}
if(!recipeList) { return <div>Loading!</div>}
return (
/*Container spanning entire screen used to center content horizontally */
<div className="w-screen min-h-screen flex justify-center">
@ -44,20 +44,24 @@ export default function RecipeListPage() {
{/* Header - remains in position when scrolling */}
<div className="sticky bg-gray-100 top-0 left-0 right-0 pb-4 border-b-2 border-gray-300">
<h1 className="content-title text-blue-900">Recipes</h1>
<div className="flex columns-4 content-stretch gap-2 items-center">
{/* Number of recipes inlist */}
<label className="label w-2/3">{recipeList.length} Recipes</label>
{/* Search field */}
<SearchField
searchString=""
onSearchStringChanged={setSearchString}
/>
{/* Add recipe button */}
<button className="primary-button"
onClick={handleAdd}
>
Add recipe
</button>
<div className="flex flex-wrap items-center gap-2 w-full">
{/* Label: left-aligned on medium+ screens, full-width on small screens */}
<div className="order-2 md:order-1 w-full md:w-auto md:flex-1">
<label className="label">{recipeList.length} Recipes</label>
</div>
{/* Search + Add button container: right-aligned on medium+ screens */}
<div className="order-1 md:order-2 flex flex-1 md:flex-none justify-end gap-2 min-w-[160px]">
<div className="flex-1 md:flex-none md:max-w-[500px]">
<SearchField onSearchStringChanged={setSearchString} />
</div>
<button
className="primary-button flex-shrink-0"
onClick={handleAdd}
>
Add recipe
</button>
</div>
</div>
</div>
{/*Content - List of recipe cards */}
@ -74,3 +78,38 @@ export default function RecipeListPage() {
</div>
)
}
// /*Container spanning entire screen used to center content horizontally */
// <div className="w-screen min-h-screen flex justify-center">
// {/* Container defining the maximum width of the content */}
// <div className="bg-gray-100 w-full min-h-screen max-w-5xl shadow-xl p-8">
// {/* Header - remains in position when scrolling */}
// <div className="sticky bg-gray-100 top-0 left-0 right-0 pb-4 border-b-2 border-gray-300">
// <h1 className="content-title text-blue-900">Recipes</h1>
// <div className="flex columns-4 content-stretch gap-2 items-center">
// {/* Number of recipes inlist */}
// <label className="label w-2/3">{recipeList.length} Recipes</label>
// {/* Search field */}
// <SearchField
// onSearchStringChanged={setSearchString}
// />
// {/* Add recipe button */}
// <button className="primary-button"
// onClick={handleAdd}
// >
// Add recipe
// </button>
// </div>
// </div>
// {/*Content - List of recipe cards */}
// <div className="grid pt-4 gap-6 grid-cols-[repeat(auto-fit,minmax(220px,1fr))]">
// {recipeList.map((recipe) => (
// <RecipeListItem
// key={recipe.id}
// title = {recipe.title}
// targetPath={getRecipeDetailUrl(recipe.id)}
// />
// ))}
// </div>
// </div>
// </div>

View file

@ -1,4 +1,5 @@
/**
* Backend URL
*/
export const API_BASE_URL = "http://localhost:4000"
//export const API_BASE_URL = "http://localhost:4000"
export const API_BASE_URL = "http://10.0.1.152:4000"

View file

@ -1,6 +1,13 @@
import { defineConfig } from "vite"
import { defineConfig, loadEnv } from "vite"
import react from "@vitejs/plugin-react"
export default defineConfig({
plugins: [react()],
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), "")
return {
plugins: [react()],
server: {
host: env.VITE_HOST || "localhost",
port: Number(env.VITE_PORT) || 5173,
},
}
})