Merge pull request #38 from rjNemo/reborn

Migrate to nuxt
This commit is contained in:
Ruidy 2024-04-28 22:15:14 +02:00
commit b2680e7d22
No known key found for this signature in database
GPG key ID: E00F51288CB857CC
31 changed files with 2286 additions and 121 deletions

1655
.aider.chat.history.md Normal file

File diff suppressed because it is too large Load diff

276
.aider.input.history Normal file
View file

@ -0,0 +1,276 @@
# 2024-12-13 21:39:55.512352
+n
# 2024-12-13 21:40:09.266770
+/add components/AppNavbar.vue
# 2024-12-13 21:41:08.087873
+change the nuxt-link to random. It must be a button that triggers the useREcipeRandom composable. If the user is not on the /random url we must navigate to it
# 2024-12-13 21:41:57.546012
+/add components/AppNavbar.vue
# 2024-12-13 22:37:21.637830
+/add package.json
# 2024-12-13 22:38:15.345890
+/add pages/[id].vue
# 2024-12-13 22:45:55.419537
+/add pages/index.vue
# 2024-12-13 22:50:08.132910
+/add app.vue
# 2024-12-13 22:57:55.838401
+/add pages/[id].vue
# 2024-12-13 22:58:23.470174
+/add components/recipe/card.vue components/recipe/index.vue components/recipe/ingredients.vue
# 2024-12-13 22:58:50.995437
+I want to add a skeleton when the page loads that matches the card and ingredient list and content layout
# 2024-12-13 23:00:37.864463
+/add pages/[id].vue
# 2024-12-13 23:10:51.785039
+/add components/AppFooter.vue
# 2024-12-13 23:17:52.755408
+/drop gitsigns:///Users/ruidy/Dev/node/meal_planner/.git//:0:components/AppFooter.vue
# 2024-12-13 23:29:25.130188
+/add README.md
# 2024-12-14 10:11:48.291791
+/add composables/useRecipeSearch.ts
# 2024-12-14 10:13:08.935799
+create a research page on the /search url taking the search query as a query parameter. use daisyui for the layout
# 2024-12-14 10:14:09.451269
+/add components/recipe/search.vue
# 2024-12-14 10:14:21.375661
+/add server/trpc/routers/recipes.ts
# 2024-12-14 10:14:28.419016
+now implement
# 2024-12-14 10:16:11.974208
+/add pages/search.vue
# 2024-12-14 10:31:29.372232
+/add components/recipe/search.vue
# 2024-12-14 10:40:24.170866
+/add .nuxt/types/imports.d.ts
# 2024-12-14 10:42:09.952541
+/add node_modules/nuxt/dist/app/composables/asyncData.d.ts
# 2024-12-14 10:46:07.185510
+/add server/trpc/routers/recipes.ts
# 2024-12-14 10:46:11.532349
+/add utils/recipes.ts
# 2024-12-14 10:46:15.907096
+/add types/recipe.ts
# 2024-12-14 15:08:57.920043
+/drop
# 2024-12-14 15:09:15.291578
+create a categories page
# 2024-12-14 15:11:08.473107
+/add server/trpc/routers/recipes.ts
# 2024-12-14 15:11:28.898754
+/drop server/trpc/routers/recipes.ts
# 2024-12-14 15:11:28.976166
+/add server/trpc/routers/recipes.ts
# 2024-12-14 15:11:32.962219
+/drop neo-tree filesystem [1]
# 2024-12-14 15:11:46.767029
+/add server/trpc/routers/categories.ts
# 2024-12-14 15:13:15.829646
+create the category page calling the router you created
# 2024-12-14 15:14:00.601188
+/add composables/useCategories.ts
# 2024-12-14 15:14:48.317381
+/add server/trpc/trpc.ts
# 2024-12-14 15:14:55.169821
+/add server/trpc/context.ts
# 2024-12-14 15:14:57.297636
+/add server/trpc/routers/index.ts
# 2024-12-14 15:16:08.866486
+/drop ~/Dev/node/meal_planner
# 2024-12-14 15:17:30.807622
+/add pages/categories.vue
# 2024-12-14 15:19:32.874574
+/add pages/search.vue
# 2024-12-14 15:19:44.835990
+improve the layout by taking example on the search page
# 2024-12-14 15:20:10.531362
+add pages/categories.vue
# 2024-12-14 15:20:56.212560
+/add src/components/Navbar.tsx
# 2024-12-14 15:20:56.245090
+/drop ~/Dev/node/meal_planner
# 2024-12-14 15:20:56.274982
+/add ~/Dev/node/meal_planner
# 2024-12-14 15:20:56.287118
+/add components/app/navbar.vue
# 2024-12-14 15:23:50.021356
+/drop
# 2024-12-14 15:24:02.867277
+/add pages/categories.vue
# 2024-12-14 15:24:35.057752
+remove the header and set a fixed height the card. use ellipsis if the description is longer that the max allowed height. make it responseive
# 2024-12-14 15:25:28.155546
+/add server/trpc/routers/categories.ts
# 2024-12-14 15:25:55.220084
+/add types/recipe.ts
# 2024-12-14 15:27:09.955135
+parse the categories fetch call response similarly to the recipes
# 2024-12-14 15:28:04.976045
+parse the categories fetch call similarly to the recipes
# 2024-12-14 15:28:22.265520
+/add types/recipe.ts
# 2024-12-14 15:28:32.201929
+/add server/trpc/routers/categories.ts
# 2024-12-14 15:29:18.224388
+/add server/trpc/routers/recipes.ts
# 2024-12-14 15:29:27.965664
+/add utils/recipes.ts
# 2024-12-14 15:31:29.546270
+/add pages/categories/index.vue
# 2024-12-14 15:31:29.727185
+/drop pages/categories/index.vue
# 2024-12-14 15:31:29.775294
+/add pages/categories/index.vue
# 2024-12-14 15:32:43.034734
+/add composables/useCategories.ts
# 2024-12-14 15:32:52.855244
+/add node_modules/zod/lib/types.d.ts
# 2024-12-14 15:33:24.249300
+/add types/category.ts
# 2024-12-14 15:38:28.584427
+/add ~/Dev/node/meal_planner
# 2024-12-14 15:38:28.657843
+/drop ~/Dev/node/meal_planner
# 2024-12-14 15:38:28.663316
+/add ~/Dev/node/meal_planner
# 2024-12-14 15:38:38.367339
+/drop ~/Dev/node/meal_planner
# 2024-12-14 15:38:38.373518
+/add ~/Dev/node/meal_planner
# 2024-12-14 15:38:49.446018
+/add components/CategoryCard.vue
# 2024-12-14 15:44:12.403700
+n
# 2024-12-14 15:45:04.918485
+add a category details page use the image as a header background with the name inside. add the description below then cards with the related recipes
# 2024-12-14 15:46:47.445160
+/add pages/category/[name].vue
# 2024-12-14 15:47:22.637607
+/add server/trpc/routers/recipes.ts
# 2024-12-14 15:47:50.065673
+/add composables/useCategoryRecipes.ts
# 2024-12-14 15:55:04.811952
+/add src/containers/Category/index.tsx
# 2024-12-14 15:55:10.429945
+/add src/services/api.ts
# 2024-12-14 18:53:11.975975
+/drop types/recipe.ts
# 2024-12-14 18:53:14.326984
+/drop utils/recipes.ts
# 2024-12-14 18:53:15.873485
+/drop composables/useCategories.ts
# 2024-12-14 18:53:20.204915
+/add composables/useCategories.ts
# 2024-12-14 18:53:43.120147
+/add ~/Dev/node/meal_planner
# 2024-12-14 18:53:43.148579
+/drop ~/Dev/node/meal_planner
# 2024-12-14 18:53:43.160247
+/add ~/Dev/node/meal_planner
# 2024-12-14 18:53:57.723720
+/add server/trpc/routers/index.ts
# 2024-12-14 19:19:40.694592
+/add ~/Dev/node/meal_planner
# 2024-12-14 19:19:40.708960
+/drop ~/Dev/node/meal_planner
# 2024-12-14 19:19:40.716279
+/add ~/Dev/node/meal_planner
# 2024-12-14 23:44:44.873544
+/drop ~/Dev/node/meal_planner
# 2024-12-14 23:46:59.255410
+/add pages/categories/[name].vue
# 2024-12-14 23:46:59.262787
+/drop pages/category/[name].vue

Binary file not shown.

Binary file not shown.

Binary file not shown.

34
.gitignore vendored
View file

@ -1,12 +1,24 @@
build/
node_modules/
.idea/
.vscode/
# Nuxt dev/build outputs
.output
.data
.nuxt
.nitro
.cache
dist
# Node dependencies
node_modules
# Logs
logs
*.log
# Misc
.DS_Store
.fleet
.idea
# Local env files
.env
.yarn/*
!.yarn/cache
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
.env.*
!.env.example

7
.gitpod.Dockerfile vendored
View file

@ -1,7 +0,0 @@
FROM gitpod/workspace-full
# Install custom tools, runtimes, etc.
# For example "bastet", a command-line tetris clone:
# RUN brew install bastet
#
# More information: https://www.gitpod.io/docs/config-docker/

View file

@ -1,6 +0,0 @@
image:
file: .gitpod.Dockerfile
tasks:
- init: yarn install && yarn build
command: yarn start

1
.prettierignore Normal file
View file

@ -0,0 +1 @@
src/

24
TODO.md
View file

@ -1,15 +1,13 @@
# TO DO
- [ ] send message after contact form validation (confirm to sender and msg+info to admin)
- [ ] Breadcrumb
- [ ] Cookie bar
- [x] code cleanup (props and refactoring)
- [ ] Back to top button
- [ ] Close Sidebar at outside click
- [ ] Take a look at some components [here](http://react-materialize.github.io/react-materialize/?path=/story/css-grid--default)
- [ ] Decoupled application and data layers. Abstract such that the front end does not know where the data comes from.
- [x] Create PageLayout component
- [ ] Use Css-in-Js
- [x] Redirect to 404
- [x] Typescript
- [x] strict typing
- [x] use bun package manager
- [x] use nuxt framework
- [x] rewrite the random page, the current landing page
- [x] rewrite the recipe page
- [ ] deploy
- [ ] nuxt image
- [x] prettier and eslint
- [ ] transition
- [ ] pwa
- [ ] seo, robots.txt
- [ ] update the README

9
app.vue Normal file
View file

@ -0,0 +1,9 @@
<template>
<div data-theme="cupcake" class="flex flex-col h-screen">
<AppNavbar />
<main class="flex-grow">
<NuxtPage />
</main>
<AppFooter />
</div>
</template>

3
assets/css/main.css Normal file
View file

@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

BIN
bun.lockb Executable file

Binary file not shown.

7
components/AppFooter.vue Normal file
View file

@ -0,0 +1,7 @@
<template>
<footer class="footer footer-center p-4 bg-base-300 text-base-content">
<aside>
<p>Copyright &copy; 2024 - Made with </p>
</aside>
</footer>
</template>

42
components/AppNavbar.vue Normal file
View file

@ -0,0 +1,42 @@
<template>
<nav class="navbar bg-base-300">
<div class="navbar-start">
<div class="dropdown">
<div tabindex="0" role="button" class="btn btn-ghost lg:hidden">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 6h16M4 12h8m-8 6h16"
/>
</svg>
</div>
<ul
tabindex="0"
class="menu menu-sm dropdown-content mt-3 z-[1] p-2 shadow bg-base-200 rounded-box w-52"
>
<li><a>Categories</a></li>
</ul>
</div>
<nuxt-link to="/" class="btn btn-ghost text-xl">
<NuxtImg src="/logo192.png" width="50" />
<span style="font-family: cursive"> Chefs </span>
</nuxt-link>
</div>
<div class="navbar-center hidden lg:flex">
<ul class="menu menu-horizontal px-1">
<li><a>Categories</a></li>
</ul>
</div>
<div class="navbar-end">
<nuxt-link to="/random" class="btn btn-primary">Random</nuxt-link>
</div>
</nav>
</template>

View file

@ -0,0 +1,29 @@
<script setup lang="ts">
defineProps<{
title: string;
pictureUrl: string;
videoUrl: string;
category: string;
origin: string;
}>();
</script>
<template>
<div class="card-body items-center text-center bg-base-200">
<h2 class="card-title">{{ title }}</h2>
<figure class="px-10 py-5">
<NuxtImg :src="pictureUrl" alt="Shoes" class="rounded-xl" />
</figure>
<div class="card-actions space-between">
<NuxtLink class="badge badge-outline" :to="videoUrl">
<Icon name="cib:youtube" color="red" />
</NuxtLink>
<div class="badge badge-secondary">
<Icon name="cil:apple" /> {{ category }}
</div>
<div class="badge badge-secondary">
<Icon name="cil:location-pin" /> {{ origin }}
</div>
</div>
</div>
</template>

View file

@ -0,0 +1,27 @@
<script setup lang="ts">
import type { Recipe } from "~/types/recipe";
defineProps<{ recipe: Recipe }>();
</script>
<template>
<div>
<div class="lg:flex space-y-4 lg:justify-evenly py-4">
<div class="card w-96 bg-base-100 shadow-xl mx-auto lg:mx-2 min-h-32">
<RecipeCard
:title="recipe.title"
:picture-url="recipe.pictureUrl"
:video-url="recipe.videoUrl"
:category="recipe.category"
:origin="recipe.origin"
/>
</div>
<RecipeIngredients :ingredients="recipe.ingredients" />
</div>
<div class="flex flex-col items-center p-4">
<h2 class="prose lg:prose-xl">Instructions</h2>
<p class="prose">{{ recipe.instructions }}</p>
</div>
</div>
</template>

View file

@ -0,0 +1,26 @@
<script setup lang="ts">
defineProps<{
ingredients: { name: string; quantity: number }[];
}>();
</script>
<template>
<div class="overflow-x-auto">
<table class="table table-s table-pin-rows table-pin-cols">
<thead>
<tr>
<th />
<td>Ingredient</td>
<td>Quantity</td>
</tr>
</thead>
<tbody>
<tr v-for="(ingredient, i) in ingredients" :key="i">
<th>{{ i + 1 }}</th>
<td>{{ ingredient.name }}</td>
<td>{{ ingredient.quantity }}</td>
</tr>
</tbody>
</table>
</div>
</template>

61
composables/useRecipe.ts Normal file
View file

@ -0,0 +1,61 @@
import type { Recipe } from "~/types/recipe";
type Keyword = "random" | "filter" | "lookup" | "search";
export default async function (keyword: Keyword, param?: string) {
const { data, pending, error } = await useAsyncData(
keyword,
async () => {
const config = useRuntimeConfig();
let url = "";
switch (keyword) {
case "random":
url = `${config.apiUrl}random.php`;
break;
case "filter":
url = "";
break;
case "lookup":
url = `${config.apiUrl}${keyword}.php?i=${param}`;
break;
case "search":
url = "";
break;
default:
throw Error("unexpected URI parameters");
}
return await $fetch(url);
},
{ lazy: true },
);
const tmp = computed(() => data.value?.meals?.[0]);
const names: string[] = [];
const quantities: number[] = [];
for (const [k, v] of Object.entries(tmp.value)) {
if (k.startsWith("strIngredient") && !!v) {
names.push(v);
} else if (k.startsWith("strMeasure") && !!v) {
quantities.push(v);
}
}
const recipe = reactive<Recipe>({
title: tmp.value.strMeal,
pictureUrl: tmp.value.strMealThumb,
videoUrl: tmp.value.strYoutube,
category: tmp.value.strCategory,
origin: tmp.value.strArea,
ingredients: names.map((name, i) => ({ name, quantity: quantities[i] })),
instructions: tmp.value.strInstructions,
});
return {
recipe,
pending,
error,
};
}

6
eslint.config.mjs Normal file
View file

@ -0,0 +1,6 @@
// @ts-check
import withNuxt from "./.nuxt/eslint.config.mjs";
export default withNuxt({
ignores: ["**/src/*"],
});

19
nuxt.config.ts Normal file
View file

@ -0,0 +1,19 @@
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
devtools: { enabled: true },
css: ["~/assets/css/main.css"],
modules: ["@nuxt/eslint", "@nuxt/image", "nuxt-icon"],
postcss: {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
},
runtimeConfig: {
// The private keys which are only available server-side
apiUrl: "",
// Keys within public are also exposed client-side
public: {},
},
ssr: true,
});

View file

@ -1,42 +1,32 @@
{
"name": "meal-planner",
"version": "0.1.0",
"name": "chefs",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.11.9",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.0.10",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-router-dom": "^6.1.1",
"react-scripts": "^5.0.0"
},
"type": "module",
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
"build": "nuxt build",
"dev": "nuxt dev --port=3009",
"generate": "nuxt generate",
"preview": "nuxt preview",
"postinstall": "nuxt prepare",
"format": "bun prettier . --write",
"lint": "eslint .",
"lint:fix": "eslint . --fix"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
"dependencies": {
"@nuxt/eslint": "^0.3.10",
"@nuxt/image": "^1.6.0",
"nuxt": "^3.11.2",
"nuxt-icon": "^0.6.10",
"vue": "^3.4.21",
"vue-router": "^4.3.0"
},
"devDependencies": {
"@types/node": "^14.14.37",
"@types/react": "^17.0.3",
"@types/react-router-dom": "^5.1.7",
"prettier": "^2.5.1",
"typescript": "^4.2.3"
"@tailwindcss/typography": "^0.5.13",
"autoprefixer": "^10.4.19",
"daisyui": "^4.10.2",
"postcss": "^8.4.38",
"prettier": "3.2.5",
"prettier-plugin-tailwindcss": "^0.5.14",
"tailwindcss": "^3.4.3"
}
}

12
pages/[id].vue Normal file
View file

@ -0,0 +1,12 @@
<script setup lang="ts">
const { params } = useRoute();
const { recipe, pending, error } = await useRecipe("lookup", params.id);
</script>
<template>
<div v-if="pending">Loading</div>
<div v-else-if="error">Failed: {{ error }}</div>
<section v-else>
<Recipe :recipe="recipe" />
</section>
</template>

14
pages/index.vue Normal file
View file

@ -0,0 +1,14 @@
<template>
<div class="hero min-h-screen bg-base-200">
<div class="hero-content flex-col lg:flex-row-reverse">
<NuxtImg src="/chef.svg" class="max-w-sm rounded-lg shadow-2xl" />
<div>
<h1 class="text-5xl font-bold prose">Eat Something New</h1>
<p class="py-6 prose">Generate a random recipe.</p>
<NuxtLink to="/random" class="btn btn-primary" external>
Random Recipe Now
</NuxtLink>
</div>
</div>
</div>
</template>

11
pages/random.vue Normal file
View file

@ -0,0 +1,11 @@
<script setup lang="ts">
const { recipe, pending, error } = await useRecipe("random");
</script>
<template>
<div v-if="pending">Loading</div>
<div v-else-if="error">Failed: {{ error }}</div>
<section v-else>
<Recipe :recipe="recipe" />
</section>
</template>

View file

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View file

@ -1,22 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<link rel="icon" href="%PUBLIC_URL%/favicon.ico"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<meta name="theme-color" content="#ff6d00"/>
<meta name="description" content="Online Meal Planner | Chef's"/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png"/>
<link rel="manifest" href="%PUBLIC_URL%/manifest.json"/>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"/>
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons"/>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Marck+Script&display=swap"/>
<title>Online Meal Planner | Chef's</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
</body>
</html>

View file

@ -27,4 +27,4 @@
"display": "standalone",
"theme_color": "#ff6d00",
"background_color": "#ffffff"
}
}

18
tailwind.config.js Normal file
View file

@ -0,0 +1,18 @@
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./components/**/*.{js,vue,ts}",
"./layouts/**/*.vue",
"./pages/**/*.vue",
"./plugins/**/*.{js,ts}",
"./app.vue",
"./error.vue",
],
theme: {
extend: {},
},
plugins: [require("@tailwindcss/typography"), require("daisyui")],
daisyui: {
themes: ["cupcake"],
},
};

View file

@ -1,29 +1,4 @@
{
"compilerOptions": {
"target": "es6",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"noUnusedParameters": true,
"noUnusedLocals": true,
"noImplicitReturns": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": [
"src"
]
// https://nuxt.com/docs/guide/concepts/typescript
"extends": "./.nuxt/tsconfig.json"
}

9
types/recipe.ts Normal file
View file

@ -0,0 +1,9 @@
export type Recipe = {
title: string;
pictureUrl: string;
videoUrl: string;
category: string;
origin: string;
ingredients: { name: string; quantity: number }[];
instructions: string;
};