Skip to content

Commit 1597f6a

Browse files
authored
Merge pull request #155 from CodeSpace-Academy/main
Bring over changes from old repo to new repo
2 parents ba071a8 + d41dc3e commit 1597f6a

25 files changed

+8887
-1075
lines changed

.github/workflows/linting.yml

Lines changed: 27 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,30 +13,32 @@ jobs:
1313
strategy:
1414
matrix:
1515
node-version: [18.x, 20.x, 22.x]
16-
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
1716

18-
steps:
19-
- uses: actions/checkout@v4
20-
21-
- name: Use Node.js ${{ matrix.node-version }}
22-
uses: actions/setup-node@v4
23-
with:
24-
node-version: ${{ matrix.node-version }}
25-
cache: 'npm'
26-
27-
- name: Cache node modules
28-
uses: actions/[email protected]
29-
with:
30-
path: node_modules
31-
key: node-modules-${{ runner.os }}-node-${{ matrix.node-version }}-${{ hashFiles('package-lock.json') }}
32-
restore-keys: |
33-
node-modules-${{ runner.os }}-node-${{ matrix.node-version }}
17+
env: # Set environment variables at the job level
18+
MONGODB_URI: ${{ secrets.MONGODB_URI }} # Ensure this is correctly set
3419

35-
# Install dependencies with --legacy-peer-deps to avoid peer conflict issues
36-
- run: npm ci --legacy-peer-deps
37-
38-
# Linting step
39-
- run: npm run lint
40-
41-
# Build step
42-
- run: npm run build --if-present
20+
steps:
21+
- uses: actions/checkout@v4
22+
- name: Use Node.js ${{ matrix.node-version }}
23+
uses: actions/setup-node@v4
24+
with:
25+
node-version: ${{ matrix.node-version }}
26+
cache: 'npm'
27+
- name: Debug MongoDB URI
28+
run: echo "MONGODB_URI is set to: $MONGODB_URI" # Use the env variable instead of the secret directly
29+
env:
30+
MONGODB_URI: ${{ secrets.MONGODB_URI }}
31+
- name: Cache node modules
32+
uses: actions/[email protected]
33+
with:
34+
path: node_modules
35+
key: node-modules-${{ runner.os }}-node-${{ matrix.node-version }}-${{ hashFiles('package-lock.json') }}
36+
restore-keys: |
37+
node-modules-${{ runner.os }}-node-${{ matrix.node-version }}
38+
- run: npm ci --legacy-peer-deps
39+
- run: npm run lint
40+
- run: npm run build --if-present
41+
- name: Run tests
42+
run: |
43+
cd backend
44+
npx mocha --config .mocharc.json

.storybook/main.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import type { StorybookConfig } from "@storybook/nextjs";
2+
3+
const config: StorybookConfig = {
4+
stories: [
5+
"../app/components/**/*.mdx",
6+
"../app/components/**/*.stories.@(js|jsx|mjs|ts|tsx)",
7+
"../app/components/**/**/*.mdx",
8+
"../app/components/**/**/*.stories.@(js|jsx|mjs|ts|tsx)",
9+
"../app/components/stories/**/*.mdx",
10+
"../app/components/stories/**/*.stories.@(js|jsx|mjs|ts|tsx)",
11+
"../app/components/stories/*.mdx",
12+
"../app/components/stories/*.stories.@(js|jsx|mjs|ts|tsx)",
13+
],
14+
addons: [
15+
"@storybook/addon-onboarding",
16+
"@storybook/addon-links",
17+
"@storybook/addon-essentials",
18+
"@chromatic-com/storybook",
19+
"@storybook/addon-interactions",
20+
"@storybook/addon-styling-webpack"
21+
],
22+
framework: {
23+
name: "@storybook/nextjs",
24+
options: {},
25+
},
26+
};
27+
export default config;

.storybook/preview.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// .storybook/preview.js
2+
3+
import type { Preview } from "@storybook/react";
4+
import '../app/global.css';
5+
6+
const preview: Preview = {
7+
parameters: {
8+
controls: {
9+
matchers: {
10+
color: /(background|color)$/i,
11+
date: /Date$/i,
12+
},
13+
},
14+
},
15+
};
16+
17+
export default preview;
18+

app/api/recipes/route.js

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,39 @@
11
import clientPromise from "../../../lib/mongodb";
22

33
/**
4-
* API route handler for fetching all recipes from the 'recipes' collection in MongoDB.
4+
* API route handler for fetching paginated recipes from the 'recipes' collection in MongoDB.
55
*
66
* @async
77
* @function
8-
* @param {Object} _req - The request object (not used in this case).
9-
* @param {Object} res - The response object used to send back the result.
10-
* @returns {Promise<void>} Sends a JSON response containing the fetched recipes or an error message.
8+
* @param {Object} req - The request object containing query parameters for pagination.
9+
* @returns {Promise<void>} Sends a JSON response containing the paginated recipes or an error message.
1110
*/
12-
export async function GET (req) {
11+
export async function GET(req) {
1312
try {
1413
// Await the MongoDB client connection
1514
const client = await clientPromise;
16-
const db = client.db('devdb'); // Connect to the 'devdb' database
15+
const db = client.db("devdb"); // Connect to the 'devdb' database
1716

18-
// Fetch all documents from the 'recipes' collection and convert them to an array
19-
const recipes = await db.collection('recipes').find({}).limit(20).toArray();
17+
// Parse the 'page' and 'limit' query parameters from the URL, with defaults
18+
const url = new URL(req.url);
19+
const page = parseInt(url.searchParams.get("page") || "1", 10);
20+
const limit = parseInt(url.searchParams.get("limit") || "20", 10);
21+
22+
// Calculate the number of documents to skip for pagination
23+
const skip = (page - 1) * limit;
24+
25+
// Fetch the paginated recipes from the collection
26+
const recipes = await db.collection("recipes")
27+
.find({})
28+
.skip(skip)
29+
.limit(limit)
30+
.toArray();
2031

2132
// Send a 200 (OK) response with the fetched recipes in JSON format
22-
return new Response(JSON.stringify({ recipes}), { status: 200 });
33+
return new Response(JSON.stringify({ recipes }), { status: 200 });
2334
} catch (e) {
24-
// Log the error to the console for debugging
25-
console.error(e);
26-
27-
// Send a 500 (Internal Server Error) response with an error message
28-
return new Response(JSON.stringify({error: 'Failed to fetch data'}), { status: 500 });
35+
36+
37+
return new Response(JSON.stringify({ error: "Failed to fetch data" }), { status: 500 });
2938
}
30-
};
39+
}

app/components/Carousel.js

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
"use client";
2+
3+
import { useState } from "react";
4+
import Image from 'next/image';
5+
6+
export default function Carousel({ images }) {
7+
const [currentIndex, setCurrentIndex] = useState(0);
8+
9+
const prevSlide = () => {
10+
setCurrentIndex((prevIndex) =>
11+
prevIndex === 0 ? images.length - 1 : prevIndex - 1
12+
);
13+
};
14+
15+
const nextSlide = () => {
16+
setCurrentIndex((prevIndex) =>
17+
prevIndex === images.length - 1 ? 0 : prevIndex + 1
18+
);
19+
};
20+
21+
// Determine if there are more than one image
22+
const hasMultipleImages = images.length > 1;
23+
24+
return (
25+
<div className="relative w-full h-64 overflow-hidden">
26+
<Image
27+
src={images[currentIndex]}
28+
alt={`Slide ${currentIndex}`}
29+
width={500} height={500} quality={75} priority
30+
className="w-full h-full object-contain"
31+
/>
32+
{/* Previous Button */}
33+
{hasMultipleImages && (
34+
<button
35+
onClick={prevSlide}
36+
className="absolute left-0 top-1/2 transform -translate-y-1/2 bg-white p-2 rounded-full shadow"
37+
>
38+
<svg
39+
xmlns="http://www.w3.org/2000/svg"
40+
className="h-6 w-6 text-gray-800"
41+
fill="none"
42+
viewBox="0 0 24 24"
43+
stroke="currentColor"
44+
>
45+
<path
46+
strokeLinecap="round"
47+
strokeLinejoin="round"
48+
strokeWidth={2}
49+
d="M15 19l-7-7 7-7"
50+
/>
51+
</svg>
52+
</button>
53+
)}
54+
{/* Next Button */}
55+
{hasMultipleImages && (
56+
<button
57+
onClick={nextSlide}
58+
className="absolute right-0 top-1/2 transform -translate-y-1/2 bg-white p-2 rounded-full shadow"
59+
>
60+
<svg
61+
xmlns="http://www.w3.org/2000/svg"
62+
className="h-6 w-6 text-gray-800"
63+
fill="none"
64+
viewBox="0 0 24 24"
65+
stroke="currentColor"
66+
>
67+
<path
68+
strokeLinecap="round"
69+
strokeLinejoin="round"
70+
strokeWidth={2}
71+
d="M9 5l7 7-7 7"
72+
/>
73+
</svg>
74+
</button>
75+
)}
76+
</div>
77+
);
78+
}

app/components/CookTimeIcon.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export function CookTimeIcon() {
2+
return (
3+
<svg fill="#000000" width="25px" height="25px" viewBox="0 0 512.853 512.853" xmlns="http://www.w3.org/2000/svg">
4+
<g>
5+
{/* Your SVG path data here */}
6+
</g>
7+
</svg>
8+
);
9+
}
10+

app/components/PrepTimeIcon.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export function PrepTimeIcon() {
2+
return (
3+
<svg fill="#000000" width="25px" height="25px" viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg">
4+
<g>
5+
{/* Your SVG path data here */}
6+
</g>
7+
</svg>
8+
);
9+
}
10+

app/components/RecipeCard.js

Lines changed: 61 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,30 @@
11
import Link from "next/link";
2-
import Image from "next/image";
2+
import Carousel from "./Carousel";
33

4+
export default function RecipeCard(recipe) {
5+
const formatDate = (dateString) => {
6+
const options = { year: "numeric", month: "long", day: "numeric" };
7+
const date = new Date(dateString);
8+
return date.toLocaleDateString(undefined, options);
9+
};
410

5-
export default function RecipeCard({ recipe }) {
6-
7-
const formatDate = (dateString) => {
8-
const options = { year: 'numeric', month: 'long', day: 'numeric' };
9-
const date = new Date(dateString);
10-
return date.toLocaleDateString(undefined, options);
11-
};
12-
11+
const totalTime = (recipe.prep || 0) + (recipe.cook || 0);
1312
return (
14-
<div>
15-
<div className=" bg-white shadow-soft rounded-lg overflow-hidden shadow hover:shadow-lg transition-shadow p-6">
16-
<Image
17-
src={recipe.images[0]}
13+
<div className=" bg-peach rounded-lg overflow-hidden shadow-md hover:shadow-2xl hover:shadow-orange-700 p-4">
14+
<Carousel
15+
images={recipe.images}
1816
alt={recipe.title}
19-
className="w-full h-40 object-contain"
20-
width={500}
21-
height={500}
22-
quality={75}
23-
priority
17+
className="w-full h-48 object-cover rounded-md"
2418
/>
25-
<div className="absolute inset-0 z-10"></div>
26-
<h3 className="text-black font-bold mt-2 text-lg text-gradient-primary">
27-
{recipe.title}
28-
</h3>
19+
<p className="text-gray-600 text-xs mt-1">Published : {formatDate(recipe.published)}</p>
20+
<div className="p-4">
21+
<h3 className="text-brown font-bold text-xl">
22+
{recipe.title}
23+
</h3>
2924

30-
<div className="flex flex-row justify-between items-center mt-2">
31-
<p className="flex flex-col items-center fill-current text-gray-400 p-4">
25+
26+
<div className="flex items-center justify-between mt-3 p-4">
27+
<p className="flex flex-col items-center fill-current text-green-800 flex-grow text-center">
3228
<svg
3329
fill="#000000"
3430
width="25px"
@@ -49,7 +45,7 @@ export default function RecipeCard({ recipe }) {
4945
{recipe.prep} mins
5046
</p>
5147

52-
<p className="flex flex-col items-center fill-current text-gray-400 flex-grow text-center">
48+
<p className="flex flex-col items-center fill-current text-green-900 flex-grow text-center">
5349
<svg
5450
fill="#000000"
5551
height="25px"
@@ -74,19 +70,47 @@ export default function RecipeCard({ recipe }) {
7470
{recipe.cook} mins
7571
</p>
7672

77-
<p>{recipe.servings}Servings</p>
73+
<p
74+
className="flex flex-col items-center fill-current text-green-800 flex-grow text-center">
75+
76+
<svg width="25px" height="25px" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="#000000">
77+
<circle cx="32" cy="32" r="24" />
78+
<polyline points="40 44 32 32 32 16" stroke-width="4px" />
79+
</svg>
7880

79-
<p>{formatDate(recipe.published)}</p>
80-
</div>
81+
{totalTime} Mins Total</p>
82+
83+
<p className="flex flex-col items-center fill-current text-green-800 flex-grow text-center">
84+
<svg
85+
fill="#000000"
86+
height="25px"
87+
width="25px"
88+
xmlns="http://www.w3.org/2000/svg"
89+
viewBox="0 0 512 512"
90+
>
91+
<g>
92+
<path
93+
d="M503.83,388.085H8.17c-4.513,0-8.17,3.657-8.17,8.17s3.657,8.17,8.17,8.17h25.333c3.795,18.624,20.3,32.681,40.029,32.681h364.936c19.728,0,36.233-14.057,40.029-32.681h25.333c4.513,0,8.17-3.657,8.17-8.17S508.343,388.085,503.83,388.085z M438.468,420.766H73.532c-10.651,0-19.733-6.831-23.105-16.34h411.147C458.201,413.935,449.119,420.766,438.468,420.766z"
94+
/>
95+
<circle cx="156.868" cy="232.851" r="8.17" />
96+
<circle cx="124.187" cy="265.532" r="8.17" />
97+
<path
98+
d="M264.17,140.421v-16.506h24.511c4.513,0,8.17-3.657,8.17-8.17c0-22.526-18.325-40.851-40.851-40.851s-40.851,18.325-40.851,40.851c0,4.513,3.657,8.17,8.17,8.17s8.17-3.657,8.17-8.17c0-13.515,10.996-24.511,24.511-24.511c10.652,0,19.738,6.83,23.111,16.34H256c-4.513,0-8.17,3.657-8.17,8.17v24.676C128.463,144.737,32.681,243.173,32.681,363.574c0,4.513,3.657,8.17,8.17,8.17s8.17-3.657,8.17-8.17c0-114.129,92.85-206.979,206.979-206.979s206.979,92.85,206.979,206.979c0,4.513,3.657,8.17,8.17,8.17s8.17-3.657,8.17-8.17C479.319,243.173,383.537,144.737,264.17,140.421z"
99+
/>
100+
</g>
101+
</svg>
81102

82-
103+
{recipe.servings} Servings
104+
</p>
105+
</div>
106+
107+
<Link
108+
href={`/recipes/${recipe._id}`}
109+
className="mt-4 block text-center text-white bg-brown rounded-full py-2 hover:bg-green-800 transition duration-200"
110+
>
111+
Get Cooking
112+
</Link>
113+
</div>
83114
</div>
84-
<Link
85-
href={`/recipes/${recipe._id}`}
86-
className="bg-gray-600 p-4 rounded-lg flex justify-end hover:bg-pink-100 hover:transform hover:scale-101 transition-all duration-300 ease-in-out"
87-
>
88-
Get Cooking
89-
</Link>
90-
</div>
91115
);
92-
}
116+
}

0 commit comments

Comments
 (0)