Source: js/components/RecipeCard.js

/**
 * Displays a custom recipe card with image, title, time, calories, and actions.
 */

import * as RecipeStore from '../database/RecipeStore.js';
import { renderCardDetails, render, renderEditPage } from '../router.js';
const RECIPE_STORE = new RecipeStore.RecipeStore();

/**
 * Custom Web Component that displays a recipe card with image, title,
 * time, calories, and action buttons for edit, delete, and favorite.
 */

class RecipeCard extends HTMLElement {

	/**
	 * Constructs a new RecipeCard custom element.
	 * Initializes shadow DOM and injects base HTML structure and styles.
	 */

	constructor() {
		super();
		this.attachShadow({ mode: 'open' });

		const articleContainer = document.createElement('article');

		const styleElement = document.createElement('style');
		styleElement.textContent = `
			:host {
				display: block;
				width: var(--card-width, 250px); 
				height: var(--card-height, 353px); 
			}

			article {
				width: 100%;
				height: 100%;
				background-color: var(--color-background-card);
				box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.25);
				border-radius: 30px;
				display: flex;
				flex-direction: column;
				align-items: center;
				text-align: center;
				font-family: "Inter", sans-serif;
			}

			.pic-box {
				position: relative;
				margin-top: 10px;
				height: 47%;
				width: 91%;
				background-color: var(--color-image-background-card);
				border-radius: 25px;

				background-size: cover;       
				background-position: center;  
				background-repeat: no-repeat;
			}

			.pic-box button {
				position: absolute;
				background: none;
				border: none;
				cursor: pointer;
				padding: 4px;
			}

			.star-btn {
				top: 10px;
				left: 10px;
			}

			.menu-btn {
				top: 10px;
				right: 10px;
			}


			.pic-box img {
				width: 30px;
				height: 30px;
			}

			.recipe-title {
				font-weight: 550;
				font-size: 22px;
				margin: 10px 0 5px 0;
			}

			.time-and-calories {
				display: flex;
				flex-direction: row;
				text-align: center;
				align-items: center;
				margin-top: 7px;
				gap: 10px;
				font-size: 14.5px;
				color: var(--color-time-and-calorites);
			}

			.time-and-calories time,
			.time-and-calories .calories {
				margin: 0;
				padding: 0;
			}

			.time-and-calories img {
				width: 15px;
			}

			.ingredients {
				padding: 6px 0 6px 0;
				font-size: 14px;
				border: none;
				border-top: 2.7px solid var(--color-ing-border);
				border-bottom: 2.7px solid var(--color-ing-border);
				margin-top: 18px;
				margin-left: 17px;
				margin-right: 17px;
				color: var(--color-text);
			}

			p {
				margin: 0;
				padding: 0;
			}
			
			.drop-down {
				position: absolute;
				top: 24%;
				right: 6%;
				width: 50%;
				height: 45%;
				background-color: white;
				border-radius: 25px;
				display: flex;
				flex-direction: column;
				// align-items: center;
				justify-content: center;
				display: none;
				box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
				z-index:10;
				overflow: hidden;
			}

			.delete, .edit {
				display: flex;
				align-items: center;
				font-size: 16px;
				gap: 8px;
				color: #525252;
				margin-left: 0;
				padding: 6px 15px;
				cursor: pointer;
				transition: background-color 0.2s ease;
			}

			.delete img,
			.edit img {
				width: 1.2rem;
				height: 1.2rem;
			}

			.delete:hover,
			.edit:hover
			 {
				background-color: #edecec;
			}
		`;

		this.shadowRoot.appendChild(articleContainer);
		this.shadowRoot.appendChild(styleElement);
	}

	/**
	 * Sets recipe data and fills the card with info and buttons.
	 * @param {Object} data - Recipe details like name, time, etc.
	 */

	set data(data) {
		// Check to see if nothing was passed in
		if (!data) {
			return;
		}

		const updateCard = async () => {
			const typeofdata = typeof data.id;
			// console.log(`data.id value = ${data.id}`);
			// console.log(`TYPE OF DATA.ID = ${typeofdata}`);
			// console.clear();
			console.log(data);
			const imageBlob = await RECIPE_STORE.getRecipeImageURL(data.id);
			const imageURL = URL.createObjectURL(imageBlob);

			const article = this.shadowRoot.querySelector('article');

			article.addEventListener('click', function (event) {

				renderCardDetails(data.id);
			});

			const starImgSrc = data.isFavorite
				? 'https://cse110-sp25-group8.github.io/final-project/assets/coloredStar.svg'
				: 'https://cse110-sp25-group8.github.io/final-project/assets/star.svg';


			article.innerHTML = `
								<div class="pic-box">
					<button class="star-btn">
						<img src="${starImgSrc}" alt="star" class="star-img">
					</button>
					<!-- <img src="${imageURL}" alt="${data.name}"> -->
					<button class="menu-btn">
						<img src="https://cse110-sp25-group8.github.io/final-project/assets/horizontal.svg" alt="menu">
					</button>
					<div class="drop-down">
						<div class="edit" role="button">
							<img src="https://cse110-sp25-group8.github.io/final-project/assets/edit.svg" alt="edit"> 
							<p>Edit</p>
						</div>

						<div class="delete" role="button">
							<img src="https://cse110-sp25-group8.github.io/final-project/assets/trash.svg" alt="delete"> 
							<p>Delete</p>
						</div>
					</div>
				</div>
				<p class="recipe-title">${data.name}</p>
				<section class="time-and-calories">
					<img src="https://cse110-sp25-group8.github.io/final-project/assets/time.svg" alt="time">
					
					<time>${data.totalTime} min</time>
					<img src="https://cse110-sp25-group8.github.io/final-project/assets/calories.svg" alt="calories">
					<p class="calories">${data.calories} kcal</p>

				</section>
				<p class="ingredients">${data.recipeCategory}, ${data.recipeCuisine}</p>
			`;

			// Add image to pic-box
			const picBox = this.shadowRoot.querySelector('.pic-box');
			picBox.style.backgroundImage = `url(${imageURL})`;

			const menuBtn = this.shadowRoot.querySelector('.menu-btn');
			const dropdown = this.shadowRoot.querySelector('.drop-down');
			const starBtn = this.shadowRoot.querySelector('.star-btn');
			const starImg = this.shadowRoot.querySelector('.star-img');

			// Show/hide dropdown
			menuBtn.addEventListener('click', (e) => {
				e.stopPropagation();
				dropdown.style.display =
					dropdown.style.display === 'flex' ? 'none' : 'flex';
			});

			// Hide dropdown if clicked outside
			this.shadowRoot.addEventListener('click', (e) => {
				const isInsideMenuBtn = menuBtn.contains(e.target);
				const isInsideDropdown = dropdown.contains(e.target);

				if (!isInsideMenuBtn && !isInsideDropdown) {
					dropdown.style.display = 'none';
				}
			});

			document.addEventListener('click', (e) => {
				const path = e.composedPath();
				const clickedInsideCard = path.includes(this);

				if (!clickedInsideCard) {
					dropdown.style.display = 'none';
				}
			});


			// Toggle favorite star
			starBtn.addEventListener('click', async (e) => {

				e.stopPropagation();

				// toggle the favorite status
				const newFavoriteStatus = !data.isFavorite;
				if (newFavoriteStatus) {
					starImg.src = 'https://cse110-sp25-group8.github.io/final-project/assets/coloredStar.svg';
				} else {
					starImg.src = 'https://cse110-sp25-group8.github.io/final-project/assets/star.svg';
				}

				data.isFavorite = newFavoriteStatus;
				try {
					// update in localStorage using the localStorageService
					const fetchAllMeta =
						JSON.parse(localStorage.getItem('recipe_metadata')) ||
						[];
					const recipeIndex = fetchAllMeta.findIndex(
						(recipe) => recipe.id === data.id
					);

					if (recipeIndex >= 0) {
						fetchAllMeta[recipeIndex].isFavorite =
							newFavoriteStatus;
						localStorage.setItem(
							'recipe_metadata',
							JSON.stringify(fetchAllMeta)
						);
					}
				} catch (error) {
					console.error('Failed to update favorite status:', error);
				}
			});

			// Edit button click
			const editBtn = this.shadowRoot.querySelector('.edit');
			editBtn.addEventListener('click', function (e) {
				e.stopPropagation();
				dropdown.style.display = 'none';
				renderEditPage(data.id);
			});

			// Delete button click
			const deleteBtn = this.shadowRoot.querySelector('.delete');
			deleteBtn.addEventListener('click', async function (e) {
				e.stopPropagation();
				await RECIPE_STORE.deleteRecipe(data);
				// this line make dropdown disapears when button is clicked
				dropdown.style.display = 'none';

				location.hash = '#/';
				render();
			});
		};

		updateCard();
	}
}

// Register the custom element
customElements.define('recipe-card', RecipeCard);