Compare commits
6 Commits
947616f0ff
...
841865b976
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
841865b976 | ||
|
|
efbf1a13ab | ||
| b6cc9e7a02 | |||
| 4b2cac054f | |||
| c3967b9abc | |||
| bb00e19385 |
@ -22,6 +22,7 @@ The FridgeBites was developed almost entirely from scratch using PHP, without re
|
||||
The website is built entirely from scratch using pure PHP, without relying on any frameworks. This was done as an experimental project to enhance personal skills. Despite this, some libraries were used to add specific functionality:
|
||||
|
||||
- [\[PHP\] PHPMailer](https://github.com/PHPMailer/PHPMailer): A library for sending emails, used for password recovery functionality.
|
||||
- [\[PHP\] FPHP](https://www.fpdf.org/): library which allows to generate PDF files with pure PHP, that is to say without using the PDFlib library.
|
||||
- [\[JS\] Swiper.js](https://github.com/nolimits4web/swiper): A highly customizable library for creating sliders and carousels.
|
||||
- [\[JS\] Toastify.js](https://github.com/apvarun/toastify-js): A lightweight library for creating beautiful and customizable toast notifications.
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
namespace Lycoreco\Apps\Ajax\Controllers;
|
||||
|
||||
use Lycoreco\Includes\BaseController;
|
||||
use Lycoreco\Includes\Model\ValidationError;
|
||||
|
||||
class AjaxController extends BaseController
|
||||
{
|
||||
@ -32,8 +33,15 @@ class AjaxController extends BaseController
|
||||
|
||||
try {
|
||||
$context['result'] = $action();
|
||||
} catch (\Exception $ex) {
|
||||
}
|
||||
catch (ValidationError $ex) {
|
||||
$context['result'] = get_ajax_error($ex->getMessage(), 400);
|
||||
return $context;
|
||||
}
|
||||
catch (\Exception $ex) {
|
||||
http_response_code(500);
|
||||
$context['result'] = get_ajax_error($ex->getMessage());
|
||||
return $context;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -1,2 +1,2 @@
|
||||
<?php
|
||||
echo $context['result'];
|
||||
echo json_encode($context['result'], JSON_PRETTY_PRINT);
|
||||
@ -1,6 +1,9 @@
|
||||
<?php
|
||||
|
||||
use Lycoreco\Apps\Recipes\Models\RecipeModel;
|
||||
use Lycoreco\Apps\Recipes\Models\{
|
||||
RecipeModel,
|
||||
RecipeUserMenu
|
||||
};
|
||||
|
||||
function get_ajax_error($message, $error_code = 500)
|
||||
{
|
||||
@ -9,13 +12,14 @@ function get_ajax_error($message, $error_code = 500)
|
||||
$error = array();
|
||||
$error['error'] = $message;
|
||||
|
||||
return json_encode($error, JSON_PRETTY_PRINT);
|
||||
return $error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajax actions
|
||||
*/
|
||||
function ajax_search() {
|
||||
function ajax_search()
|
||||
{
|
||||
$search_query = $_POST['query'] ?? null;
|
||||
if (!isset($search_query)) {
|
||||
return get_ajax_error("Missing 'query' parameter.", 400);
|
||||
@ -37,5 +41,59 @@ function ajax_search() {
|
||||
$result['count'] = count($recipes);
|
||||
$result['result'] = $recipes;
|
||||
|
||||
return json_encode($result, JSON_PRETTY_PRINT);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
function ajax_usermenu()
|
||||
{
|
||||
$recipe_id = $_POST['recipe_id'] ?? null;
|
||||
$dayofweek = $_POST['dayofweek'] ?? null;
|
||||
|
||||
if (!CURRENT_USER) {
|
||||
return get_ajax_error('You are not authorized', 401);
|
||||
}
|
||||
if (!isset($recipe_id)) {
|
||||
return get_ajax_error("Missing 'recipe_id' parameter.", 400);
|
||||
}
|
||||
if (!isset($dayofweek)) {
|
||||
return get_ajax_error("Missing 'dayofweek' parameter.", 400);
|
||||
}
|
||||
$result = array();
|
||||
|
||||
$user_menu = RecipeUserMenu::get(array(
|
||||
[
|
||||
'name' => 'obj.recipe_id',
|
||||
'type' => '=',
|
||||
'value' => $recipe_id
|
||||
],
|
||||
[
|
||||
'name' => 'obj.user_id',
|
||||
'type' => '=',
|
||||
'value' => CURRENT_USER->get_id()
|
||||
]
|
||||
));
|
||||
// If user choose optiopn 'remove'
|
||||
if($dayofweek == 'remove') {
|
||||
if($user_menu) {
|
||||
$user_menu->delete();
|
||||
$result['success'] = 'This recipe was removed from your list';
|
||||
return $result;
|
||||
}
|
||||
else {
|
||||
return get_ajax_error("This recipe in your menu is not exists", 400);
|
||||
}
|
||||
}
|
||||
|
||||
// If not exists, add new recipe in user menu
|
||||
if(!$user_menu) {
|
||||
$user_menu = new RecipeUserMenu();
|
||||
$user_menu->field_recipe_id = $recipe_id;
|
||||
$user_menu->field_user_id = CURRENT_USER->get_id();
|
||||
}
|
||||
|
||||
$user_menu->field_dayofweek = $dayofweek;
|
||||
$user_menu->save();
|
||||
|
||||
$result['success'] = 'You have successfully added the recipe to your menu.';
|
||||
return $result;
|
||||
}
|
||||
|
||||
@ -2,7 +2,9 @@
|
||||
|
||||
namespace Lycoreco\Apps\Recipes\Controllers;
|
||||
|
||||
use Lycoreco\Apps\Recipes\Models\IngredientInRecipeModel;
|
||||
use Lycoreco\Apps\Recipes\Models\RecipeModel;
|
||||
use Lycoreco\Apps\Users\Models\UserModel;
|
||||
use Lycoreco\Includes\BaseController;
|
||||
use Lycoreco\Includes\Routing\HttpExceptions;
|
||||
|
||||
@ -33,8 +35,26 @@ class SingleRecipeController extends BaseController
|
||||
public function get_context_data()
|
||||
{
|
||||
$context = parent::get_context_data();
|
||||
$recipe = $this->get_model();
|
||||
|
||||
$context['recipe'] = $this->get_model();
|
||||
|
||||
$context['recipe'] = $recipe;
|
||||
|
||||
$context['author'] = UserModel::get(array(
|
||||
[
|
||||
'name' => 'obj.id',
|
||||
'type' => '=',
|
||||
'value' => $recipe->field_author_id
|
||||
]
|
||||
));
|
||||
|
||||
$context['ingredients'] = IngredientInRecipeModel::filter(array(
|
||||
[
|
||||
'name' => 'obj.recipe_id',
|
||||
'type' => '=',
|
||||
'value' => $recipe->get_id()
|
||||
]
|
||||
));
|
||||
|
||||
return $context;
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@ class IngredientInRecipeModel extends BaseModel
|
||||
public $field_ingredient_id;
|
||||
public $field_recipe_id;
|
||||
public $field_amount;
|
||||
|
||||
|
||||
static protected $search_fields = ['tb1.name'];
|
||||
public $ingredient_name;
|
||||
public $ingredient_unit;
|
||||
@ -24,10 +24,10 @@ class IngredientInRecipeModel extends BaseModel
|
||||
|
||||
static protected $table_name = 'recipe_ingredients';
|
||||
static protected $table_fields = [
|
||||
'id' => 'int',
|
||||
'id' => 'int',
|
||||
'ingredient_id' => 'int',
|
||||
'recipe_id' => 'int',
|
||||
'amount' => 'int'
|
||||
'recipe_id' => 'int',
|
||||
'amount' => 'int'
|
||||
];
|
||||
public static function init_table()
|
||||
{
|
||||
@ -47,4 +47,9 @@ class IngredientInRecipeModel extends BaseModel
|
||||
{
|
||||
return $this->field_amount . ' ' . $this->ingredient_unit;
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return $this->ingredient_name;
|
||||
}
|
||||
}
|
||||
@ -17,8 +17,8 @@ class RecipeModel extends BaseModel
|
||||
|
||||
public $category_name;
|
||||
|
||||
const STATUS = [[ 'publish', 'Publish' ], [ 'pending', 'Pending' ]];
|
||||
|
||||
const STATUS = [['publish', 'Publish'], ['pending', 'Pending']];
|
||||
|
||||
static protected $search_fields = ['obj.title'];
|
||||
static protected $table_name = 'recipes';
|
||||
|
||||
@ -31,22 +31,22 @@ class RecipeModel extends BaseModel
|
||||
],
|
||||
[
|
||||
'field' => [
|
||||
|
||||
|
||||
],
|
||||
'join_table' => 'recipe_ingredients tb2 ON tb2.recipe_id = obj.id'
|
||||
]
|
||||
);
|
||||
static protected $table_fields = [
|
||||
'id' => 'int',
|
||||
'title' => 'string',
|
||||
'instruction' => 'string',
|
||||
'image_url' => 'string',
|
||||
'id' => 'int',
|
||||
'title' => 'string',
|
||||
'instruction' => 'string',
|
||||
'image_url' => 'string',
|
||||
'estimated_time' => 'int',
|
||||
'estimated_price' => 'float',
|
||||
'category_id' => 'int',
|
||||
'author_id' => 'int',
|
||||
'status' => 'string',
|
||||
'created_at' => 'DateTime'
|
||||
'category_id' => 'int',
|
||||
'author_id' => 'int',
|
||||
'status' => 'string',
|
||||
'created_at' => 'DateTime'
|
||||
];
|
||||
public static function init_table()
|
||||
{
|
||||
@ -68,7 +68,7 @@ class RecipeModel extends BaseModel
|
||||
);');
|
||||
return $result;
|
||||
}
|
||||
public function get_price()
|
||||
public function get_price()
|
||||
{
|
||||
return $this->field_estimated_price . '$';
|
||||
}
|
||||
@ -76,10 +76,20 @@ class RecipeModel extends BaseModel
|
||||
{
|
||||
return ucfirst($this->field_status);
|
||||
}
|
||||
public function get_image_url() {
|
||||
if($this->field_image_url)
|
||||
public function get_image_url()
|
||||
{
|
||||
if ($this->field_image_url)
|
||||
return MEDIA_URL . $this->field_image_url;
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function get_html_instruction(): string
|
||||
{
|
||||
return nl2br(trim($this->field_instruction));
|
||||
}
|
||||
|
||||
public function get_time(){
|
||||
return $this->field_estimated_time . " minutes";
|
||||
}
|
||||
}
|
||||
57
apps/Recipes/Models/RecipeUserMenu.php
Normal file
57
apps/Recipes/Models/RecipeUserMenu.php
Normal file
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace Lycoreco\Apps\Recipes\Models;
|
||||
|
||||
use Lycoreco\Includes\Model\BaseModel;
|
||||
|
||||
class RecipeUserMenu extends BaseModel
|
||||
{
|
||||
public $field_dayofweek;
|
||||
public $field_recipe_id;
|
||||
public $field_user_id;
|
||||
public $field_created_at;
|
||||
|
||||
static protected $table_name = 'recipe_usermenu';
|
||||
static protected $table_fields = [
|
||||
'id' => 'int',
|
||||
'dayofweek' => 'string',
|
||||
'recipe_id' => 'int',
|
||||
'user_id' => 'int',
|
||||
'created_at' => 'DateTime'
|
||||
];
|
||||
|
||||
public static function init_table()
|
||||
{
|
||||
$result = db_query('CREATE TABLE ' . static::$table_name . ' (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
|
||||
dayofweek VARCHAR(150) NOT NULL,
|
||||
recipe_id INT NOT NULL,
|
||||
user_id INT NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
FOREIGN KEY (recipe_id) REFERENCES recipes(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
);');
|
||||
return $result;
|
||||
}
|
||||
public function valid()
|
||||
{
|
||||
require_once INCLUDES_PATH . '/Const/recipes.php';
|
||||
|
||||
if(!in_array($this->field_dayofweek, DAYS_OF_WEEK))
|
||||
return ['Day of Week is not valid'];
|
||||
|
||||
$recipe = RecipeModel::get(array(
|
||||
[
|
||||
'name' => 'obj.id',
|
||||
'type' => '=',
|
||||
'value' => $this->field_recipe_id
|
||||
]
|
||||
));
|
||||
if(!$recipe)
|
||||
return ['Recipe is not exists'];
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -31,7 +31,7 @@ the_header(
|
||||
<i class="fa-solid fa-magnifying-glass search-icon"></i>
|
||||
<input type="text" class="search-input" placeholder="Search categories...">
|
||||
</div>
|
||||
<!-- All labels and ids are the same for template design purposes (all labels point to same checkbox) -->
|
||||
|
||||
<div class="filters-checkboxes">
|
||||
<ul>
|
||||
<?php foreach ($context['categories'] as $cat):
|
||||
|
||||
@ -1,3 +1,133 @@
|
||||
<pre>
|
||||
<?php var_dump($context['recipe']); ?>
|
||||
</pre>
|
||||
<?php
|
||||
require_once(INCLUDES_PATH . '/Const/recipes.php');
|
||||
the_header(
|
||||
$context['recipe']->field_title,
|
||||
'This is a single recipe page where you can view the details of the recipe, including ingredients, instructions, and more.',
|
||||
'recipe',
|
||||
[
|
||||
|
||||
['keywords', 'terms, use, conditions']
|
||||
]
|
||||
);
|
||||
?>
|
||||
|
||||
<div class="container">
|
||||
<div class="single-recipe">
|
||||
<div class="single-recipe-info">
|
||||
<div class="single-recipe-info__image">
|
||||
<img src="<?= $context['recipe']->get_image_url(); ?>"
|
||||
alt="<?php echo $context['recipe']->field_title; ?>">
|
||||
</div>
|
||||
<div class="single-recipe-info__details">
|
||||
<div class="single-recipe-title">
|
||||
<h1 class="title"><?php echo $context['recipe']->field_title; ?></h1>
|
||||
</div>
|
||||
<div class="single-recipe-data">
|
||||
<div class="single-recipe-data__item">
|
||||
<span class="data-name">Category: </span>
|
||||
<span class="data"><?= $context['recipe']->category_name ?></span>
|
||||
</div>
|
||||
<div class="single-recipe-data__item">
|
||||
<span class="data-name">Author: </span>
|
||||
<span class="data"><?= $context['author']->field_username ?></span>
|
||||
</div>
|
||||
<div class="single-recipe-data__item">
|
||||
<span class="data-name">Estimated Price: </span>
|
||||
<span class="data"><?php echo $context['recipe']->get_price(); ?></span>
|
||||
</div>
|
||||
<div class="single-recipe-data__item">
|
||||
<span class="data-name">Time To Make: </span>
|
||||
<span class="data"><?php echo $context['recipe']->get_time(); ?></span>
|
||||
</div>
|
||||
<div class="single-recipe-data__item">
|
||||
<span class="data-name">Ingredients: </span>
|
||||
<span class="data"><?= join(", ", $context['ingredients']) ?></span>
|
||||
</div>
|
||||
<div class="single-recipe-data__item">
|
||||
<span class="data-name">Date Created: </span>
|
||||
<span class="data"><?php echo $context['recipe']->field_created_at; ?></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="button-ctrl">
|
||||
<select class="hidden" name="daily-meal-select" id="daily-meal-day">
|
||||
<?php foreach (DAYS_OF_WEEK as $day): ?>
|
||||
<option value="<?= $day ?>"><?= ucfirst($day) ?></option>
|
||||
<?php endforeach; ?>
|
||||
<option value="remove">Remove From List</option>
|
||||
</select>
|
||||
<div class="day-select-wrapper">
|
||||
<div class="hover-anim">
|
||||
<button type="button" href="#" id="day-select" class="btn btn-primary day-select">Add to
|
||||
list</button>
|
||||
<i class="fa-solid fa-list select-icon"></i>
|
||||
</div>
|
||||
<div id="custom-select-dropdown" class="custom-select-dropdown hidden">
|
||||
<?php foreach (DAYS_OF_WEEK as $day): ?>
|
||||
<div class="dropdown-item hover-anim" data-value="<?= $day?>">
|
||||
<?= ucfirst($day) ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<div class="dropdown-item hover-anim" data-value="remove">Remove From List</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="small-btns">
|
||||
<button class="btn btn-secondary btn-small hover-anim" title="Add To Favorites">
|
||||
<i class="fa-regular fa-heart"></i>
|
||||
</button>
|
||||
<a class="btn btn-secondary btn-small hover-anim"
|
||||
href="<?php the_permalink('recipes:export-pdf', [$context['recipe']->get_id()]); ?>"
|
||||
target="_blank" title="Export As PDF">
|
||||
<i class="fa-solid fa-download"></i>
|
||||
</a>
|
||||
<button class="btn btn-secondary btn-small hover-anim" id="qr-btn" title="Get QR Code">
|
||||
<i class="fa-solid fa-qrcode"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div id="overlay" class="hidden"></div>
|
||||
<div class="qr-popup hidden">
|
||||
<div id="qrcode"></div>
|
||||
<a id="downloadLink" class="btn btn-primary hover-anim" href="#" download="recipe-qr.png">
|
||||
Download QR Code
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="single-recipe-content">
|
||||
<div class="single-instructions">
|
||||
<h2 class="title">Instructions</h2>
|
||||
|
||||
<?= $context['recipe']->get_html_instruction(); ?>
|
||||
</div>
|
||||
<div class="single-ingredients">
|
||||
<h2 class="title">Ingredients</h2>
|
||||
<table class="ingredients-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Count</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php
|
||||
foreach ($context['ingredients'] as $ing) {
|
||||
?>
|
||||
<tr>
|
||||
<td><?= $ing; ?></td>
|
||||
<td><?= $ing->get_count(); ?></td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<?php the_footer(array(
|
||||
ASSETS_PATH . '/js/single.js',
|
||||
)); ?>
|
||||
@ -641,7 +641,6 @@ hr {
|
||||
|
||||
label span {
|
||||
color: #015847;
|
||||
/* Example: make the asterisk red */
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@ -655,7 +654,7 @@ label span {
|
||||
|
||||
.btn-secondary {
|
||||
background-color: var(--button-secondary);
|
||||
height: 35px;
|
||||
cursor: pointer;
|
||||
max-width: 225px;
|
||||
color: var(--common-text);
|
||||
}
|
||||
@ -766,10 +765,10 @@ label span {
|
||||
.filters__search .search-input {
|
||||
margin-right: 0;
|
||||
width: 200px;
|
||||
|
||||
|
||||
}
|
||||
|
||||
.filters-checkboxes ul{
|
||||
.filters-checkboxes ul {
|
||||
list-style: none;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
@ -777,30 +776,334 @@ label span {
|
||||
column-gap: 8px;
|
||||
padding: 0;
|
||||
max-height: 100px;
|
||||
overflow-y: auto;
|
||||
overflow-y: auto;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 15px;
|
||||
|
||||
}
|
||||
|
||||
.filters-checkboxes ul li{
|
||||
.filters-checkboxes ul li {
|
||||
font-family: var(--common-font);
|
||||
font-size: 14px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.filters-form .btn{
|
||||
.filters-form .btn {
|
||||
width: 200px;
|
||||
}
|
||||
@media (max-width: 768px){
|
||||
|
||||
.catalog-items{
|
||||
|
||||
.single-recipe {
|
||||
margin-top: 46px;
|
||||
}
|
||||
|
||||
.single-recipe-title .title {
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
.single-recipe-info {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.single-recipe-info__image {
|
||||
width: 350px;
|
||||
height: 260px;
|
||||
overflow: hidden;
|
||||
border-radius: 10px;
|
||||
margin-right: 25px;
|
||||
}
|
||||
|
||||
.single-recipe-info__image img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.single-recipe-data {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.single-recipe-data__item {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.single-recipe-data__item .data-name {
|
||||
color: var(--meta-color);
|
||||
margin-right: 10px;
|
||||
flex: 0 0 120px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.single-recipe-data__item .data {
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.day-select {
|
||||
position: relative;
|
||||
width: 230px;
|
||||
border: 1px solid #00775F;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.day-select-wrapper {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.btn-ctrl {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
}
|
||||
|
||||
.select-icon {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background-color: #008C71;
|
||||
color: #fff;
|
||||
padding: 10px;
|
||||
border-top-right-radius: 10px;
|
||||
border-bottom-right-radius: 10px;
|
||||
}
|
||||
|
||||
.btn-small {
|
||||
width: 35px;
|
||||
text-align: center;
|
||||
padding: 0;
|
||||
border: 2px solid var(--input-border);
|
||||
}
|
||||
|
||||
.button-ctrl {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.custom-select-dropdown {
|
||||
position: absolute;
|
||||
top: 110%;
|
||||
left: 0;
|
||||
z-index: 10;
|
||||
background: white;
|
||||
border: 1px solid var(--input-border);
|
||||
border-radius: 10px;
|
||||
width: 100%;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
font-size: 16px;
|
||||
padding: 8px 12px;
|
||||
cursor: pointer;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.custom-select-dropdown .dropdown-item:last-child {
|
||||
border-top: 1px solid var(--input-border);
|
||||
}
|
||||
|
||||
.dropdown-selected {
|
||||
background-color: #00775F;
|
||||
color: #fff;
|
||||
|
||||
}
|
||||
|
||||
.qr-popup {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #fff;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
#qrcode img {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
#overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
z-index: 9998;
|
||||
}
|
||||
|
||||
.btn-small,
|
||||
.btn-small i {
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
.single-recipe-content h2 {
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
.single-recipe-content {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-top: 30px;
|
||||
gap: 40px;
|
||||
/* flex-wrap: wrap; */
|
||||
}
|
||||
|
||||
.single-instructions{
|
||||
width: 50%;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.single-ingredients{
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.ingredients-table{
|
||||
width: 400px;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
border: 1px solid #b3b3b3;
|
||||
border-collapse: separate;
|
||||
}
|
||||
|
||||
.ingredients-table th{
|
||||
background-color: #008B70;
|
||||
color: #fff;
|
||||
padding: 10px 20px;
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
.ingredients-table td{
|
||||
padding: 10px 20px;
|
||||
border-top: 2px solid #b3b3b3;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
|
||||
.catalog-items {
|
||||
width: 100%;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
|
||||
.single-recipe-content {
|
||||
flex-wrap: wrap;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.single-instructions,
|
||||
.single-ingredients {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ingredients-table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
.single-recipe-info {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.single-recipe-info__image {
|
||||
width: 100%;
|
||||
max-width: 350px;
|
||||
height: auto;
|
||||
margin-right: 0;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.single-recipe-info__image img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.single-recipe-info__details {
|
||||
width: 100%;
|
||||
padding: 0 15px;
|
||||
}
|
||||
|
||||
.single-recipe-title .title {
|
||||
text-align: center;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.single-recipe-data__item {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.single-recipe-data__item .data-name {
|
||||
margin-right: 0;
|
||||
margin-bottom: 2px;
|
||||
flex: none;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.button-ctrl {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.small-btns{
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.day-select-wrapper,
|
||||
.btn-small,
|
||||
.btn-small i {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.btn-small{
|
||||
width: 35px;
|
||||
}
|
||||
|
||||
.custom-select-dropdown {
|
||||
width: 100%;
|
||||
left: 0;
|
||||
max-width: 230px;
|
||||
}
|
||||
|
||||
.qr-popup {
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
|
||||
|
||||
@ -880,39 +1183,44 @@ label span {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.catalog{
|
||||
.catalog {
|
||||
flex-direction: column-reverse;
|
||||
align-items: center;
|
||||
margin-bottom: 46px;
|
||||
}
|
||||
.filters{
|
||||
|
||||
.filters {
|
||||
position: static;
|
||||
margin-bottom: 46px;
|
||||
}
|
||||
.catalog-items{
|
||||
|
||||
.catalog-items {
|
||||
margin-right: 0;
|
||||
justify-items: center;
|
||||
align-items: center;
|
||||
padding: 5px;
|
||||
}
|
||||
.catalog-recipe{
|
||||
|
||||
.catalog-recipe {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 200px;
|
||||
}
|
||||
}
|
||||
@media (max-width: 465px){
|
||||
.catalog-recipe{
|
||||
width: 150px;
|
||||
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 400px){
|
||||
.catalog-items{
|
||||
@media (max-width: 465px) {
|
||||
.catalog-recipe {
|
||||
width: 150px;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 400px) {
|
||||
.catalog-items {
|
||||
width: 100%;
|
||||
grid-template-columns: 1fr;
|
||||
margin-right: 0;
|
||||
|
||||
71
assets/js/single.js
Normal file
71
assets/js/single.js
Normal file
@ -0,0 +1,71 @@
|
||||
const toggleBtn = document.getElementById('day-select');
|
||||
const dropdown = document.getElementById('custom-select-dropdown');
|
||||
|
||||
toggleBtn.addEventListener('click', () => {
|
||||
dropdown.classList.toggle('hidden');
|
||||
});
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!dropdown.contains(e.target) && !toggleBtn.contains(e.target)) {
|
||||
dropdown.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
const options = document.querySelectorAll('.dropdown-item');
|
||||
|
||||
options.forEach(option => {
|
||||
option.addEventListener('click', () => {
|
||||
const selectedValue = option.getAttribute('data-value');
|
||||
|
||||
options.forEach(opt => opt.classList.remove('dropdown-selected'));
|
||||
|
||||
if (selectedValue === 'remove') {
|
||||
toggleBtn.textContent = 'Add to list';
|
||||
} else {
|
||||
toggleBtn.textContent = option.textContent;
|
||||
option.classList.add('dropdown-selected');
|
||||
}
|
||||
|
||||
dropdown.classList.add('hidden');
|
||||
|
||||
alert(`You selected: ${selectedValue}`);
|
||||
});
|
||||
});
|
||||
|
||||
const qrContainer = document.getElementById("qrcode");
|
||||
const downloadLink = document.getElementById("downloadLink");
|
||||
const qrBtn = document.getElementById("qr-btn");
|
||||
const qrPopup = document.querySelector(".qr-popup");
|
||||
const overlay = document.getElementById('overlay');
|
||||
|
||||
qrBtn.addEventListener("click", () => {
|
||||
|
||||
qrPopup.classList.toggle("hidden")
|
||||
overlay.classList.toggle("hidden")
|
||||
|
||||
qrContainer.innerHTML = "";
|
||||
|
||||
|
||||
new QRCode(qrContainer, {
|
||||
text: window.location.href,
|
||||
width: 200,
|
||||
height: 200
|
||||
});
|
||||
|
||||
|
||||
setTimeout(() => {
|
||||
const qrImg = qrContainer.querySelector("img");
|
||||
if (qrImg) {
|
||||
downloadLink.href = qrImg.src;
|
||||
}
|
||||
}, 500);
|
||||
});
|
||||
|
||||
document.addEventListener('click', (e) =>{
|
||||
if(!qrPopup.contains(e.target) && !qrBtn.contains(e.target)){
|
||||
qrPopup.classList.add("hidden");
|
||||
overlay.classList.add("hidden");
|
||||
}
|
||||
});
|
||||
1
assets/qrcode/qrcode.min.js
vendored
Normal file
1
assets/qrcode/qrcode.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -20,7 +20,7 @@
|
||||
|
||||
<script src="<?php echo ASSETS_PATH . '/js/main.js' ?>"></script>
|
||||
<script src="<?php echo ASSETS_PATH . '/toastify/toastify-js.js' ?>"></script>
|
||||
|
||||
<script src="<?php echo ASSETS_PATH . '/qrcode/qrcode.min.js' ?>"></script>
|
||||
<?php foreach($scripts as $script): ?>
|
||||
<script src="<?php echo $script ?>"></script>
|
||||
<?php endforeach; ?>
|
||||
|
||||
5
includes/Const/recipes.php
Normal file
5
includes/Const/recipes.php
Normal file
@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
define('DAYS_OF_WEEK', array(
|
||||
'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'
|
||||
));
|
||||
Loading…
x
Reference in New Issue
Block a user